-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[prototype]Upgrade Swagger 2 to OpenAPI 3 #916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a9656fe to
a8c0182
Compare
69ee943 to
dcd7808
Compare
3e6faea to
af3729b
Compare
| @@ -42,16 +42,6 @@ describe('jsonToSchemaObject', () => { | |||
| propertyConversionTest(allOfDef, expectedAllOf); | |||
| }); | |||
|
|
|||
| it('converts definitions', () => { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OpenAPI3 schemaObject does not have definitions under it.
af3729b to
1952aef
Compare
| @@ -270,12 +274,6 @@ export function jsonToSchemaObject(jsonDef: JsonDefinition): SchemaObject { | |||
| result.allOf = _.map(json.allOf, item => jsonToSchemaObject(item)); | |||
| break; | |||
| } | |||
| case 'definitions': { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please preserve the conversion here as we've discussed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked with @shimks , the broken scenario will be:
@model
class Foo {
@property
bar: Bar;
@property
baz: Baz;
};
@model
class Bar {
@property
name: string
};
@model
class Baz {
@property
name: string
};
class FooController {
@get('/foo')
find(@param(<a_param_spec>) foo: Foo): void { }
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed it in my PR.
| if (openapiSchema.definitions) { | ||
| for (const key in openapiSchema.definitions) { | ||
| spec.definitions[key] = openapiSchema.definitions[key]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure with the newest conversion introduced, that nested components/schemas don't exist. May need to fiddle around with the test to make sure this is the case.
|
Progress: An article about the difference between v2 and v3:
|
|
@shimks thanks for reminding :) nope I didn't plan to add tests for |
packages/rest/src/parser.ts
Outdated
| ]; | ||
| requestBodyIndex = i ? i : 0; | ||
| } | ||
| if ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am refactoring the requestBody insert function to make the logic neater then these.
package.json
Outdated
| "test": "node packages/build/bin/run-nyc npm run mocha", | ||
| "mocha": "node packages/build/bin/run-mocha \"packages/*/DIST/test/**/*.js\" \"packages/cli/test/*.js\"", | ||
| "mocha": | ||
| "node packages/build/bin/run-mocha \"packages/*/DIST/test/**/*.js\" \"packages/cli/test/*.js\"", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The formatting changes for package.json should be reverted. Make sure you IDE won't format it automatically.
| port: opts.port || 3000, | ||
| // hardcoded now, about allowing configured `basePath`, | ||
| // see issue https://github.com/strongloop/loopback-next/issues/914 | ||
| // is it '/' or '/api'? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The explorer is loaded fine, so I think '/' is good.
| greetWithArray(@requestBody() name: string[]) {} | ||
| @post('/greetingWithObject') | ||
| greetWitObejct(@requestBody() name: object) {} | ||
| // @post('/greetingWithInteger') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shortcut hasn't implemented yet
8b06e2e to
d15def6
Compare
a9850d2 to
e41f0b5
Compare
acb95cd to
f810b7a
Compare
f810b7a to
5e6ee27
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jannyHou great work!
I took a quick and relatively high-level look at your proposed changes. See my comments below, but please don't address them yet!
In order to speed up review of this huge pull request, and to avoid too many merge conflicts, I am proposing to treat this pull request as a spike prototype demonstrating the direction we want to take (see https://gist.github.com/bajtos/58eb9a14ad563a3e7a349ad6a4d0f965#major-changes-and-large-patches) and start making the actual changes in a series of smaller follow-up pull requests.
-
PullRequest1 - OAIv3 types. Start by adding a new package
openapi-spec-types. This package will provide types needed to support OpenAPISpec v3 only (no Swagger please). Keep the oldopenapi-specpackage around with no modifications, so that all existing code keeps working. On the second though, I thinkopenapi-v3-typeswould be a better package name? -
PullRequest2 - OAIv3 decorators. Copy
openapi-v2toopenapi-v3and upgrade it to OAI v3. The first commit should be just a verbatim copy of allopenapi-v2files toopenapi-v3, the follow-up commits should make the necessary changes. This way we can review what stays same and what is changing compared to v2. Keepopenapi-v2around with no modifications, this way all existing consumers will stay functional. -
PullRequest3 - Upgrade REST from Swagger to OAIv3. The final integration of the new code added by your previous two pull requests and the existing REST layer.
-
PullRequest4 - Remove everything Swagger related. Packages like
openapi-v2andopenapi-speccan be deleted now.
It may be possible to split some of the steps above into even smaller chunks. Please do so if you can find such a meaningful split.
A comment related to this big pull request:
This diff entry looks suspicious to me:
packages/openapi-v2/CHANGELOG.md → packages/openapi-v3/CHANGELOG.md`
I think the new module should start with an empty CHANGELOG?
|
|
||
| * easy to extract common suger interfaces | ||
| * less packages to maintain, clear layout to manage different versions | ||
| * if we only support one version, export types from one package, no need to update each dependant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we have already agreed to support only a single version of OpenAPI spec?
IMO, we should have a single repo describing only the current (latest) version of OpenAPI spec.
When a new, backwards-incompatible version of OAI is released, then we will release a new semver-major version of this package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we should have a single repo describing only the current (latest) version of OpenAPI spec.
When a new, backwards-incompatible version of OAI is released, then we will release a new semver-major version of this package.
Let me take that back. Considering the effort needed to upgrade between OAI versions, I think it's better to have a new package for each major revision of OAI spec, because it makes it easier for us to split the work into independent chunks.
| Discussion: | ||
|
|
||
| If people decorate an argument with no input as `@requestBody() foo: Foo`, | ||
| I make `application/json` as the default content, is it ok? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please.
As a user building JSON/REST APIs, I'd prefer to define request-body parameters as follows:
async createTodo(
@requestBody(Todo)
todo: Todo,
) {
}In my example, @requestBody(Todo) performs two steps under the hood:
- call
repository-json-schemato convertTodomodel definition to JSON Schema (see feat(json-schema): add modules to convert TS models to JSON Schema #860 by @shimks) - convert this "shortcut" definition into a valid request body spec, e.g.
{ content: { 'application/json': { schema: TodoSchema } } }
However, considering how large this patch already is, let's leave this syntactic sugar out of scope of this initial pull request (but in the scope of the OAIv3 user story!)
|
|
||
| ## Parameter Serialization | ||
|
|
||
| * Complex Serialization in Form Data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add more details, e.g. a sample HTTP request? I think this can (and should) be left out of the initial pull request, possibly out of the MVP scope too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ## Parameter Serialization | ||
|
|
||
| * Complex Serialization in Form Data | ||
| * Serialization in parameter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am afraid I have no idea what does this mean, could you please add more details?
| ## additional properties | ||
|
|
||
| Dictionaries, HashMaps and Associative Arrays | ||
| https://swagger.io/docs/specification/data-models/dictionaries/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly here. Is this something already done and missing migration docs, or is it something that needs implementation? I am proposing to leave it out of the initial pull request, possible out of the MVP scope.
| import * as SwaggerParser from 'swagger-parser'; | ||
| import {OpenApiSpec} from '@loopback/openapi-spec'; | ||
| import {OpenApiSpec} from '@loopback/openapi-spec-types'; | ||
| const validator = require('swagger2openapi/validate.js'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the TypeScript compiler able to infer the type of validator, or is it falling back to default any? It would be great to have an explicit type specified for this function, even if it means we have to write it ourselves.
Actually, I care most about the type of promisifiedValidator, so if we are going to write the type def ourselves, then it's enough to describe promisifiedValidator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bajtos hmm, could you elaborate more about
Is the TypeScript compiler able to infer the type of validator
?
Would Function the expected type or you also expect the TypeScript compiler to know its input&return types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would Function the expected type or you also expect the TypeScript compiler to know its input&return types?
Yes, I'd like the typescript compiler to know the type of input arguments and the type of the returned value.
| const validator = require('swagger2openapi/validate.js'); | ||
| import * as util from 'util'; | ||
| const promisify = util.promisify; | ||
| const promisifiedValidator = promisify(validator.validate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this naming convention rather unusual. Functions and methods should start with a verb.
I am proposing to use the name promisifyAsync instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am proposing to use the name promisifyAsync instead.
Uh oh, I meant validateAsync.
| // TODO(bajtos) contribute these improvements to swagger-parser | ||
| if (!spec.swagger) { | ||
| const opts = {}; | ||
| if (!spec.openapi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these explicit checks still required? I added them only because swagger-parser was reporting unhelpful errors in these edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just copy paste them from the old swagger check :-p I thought they are pre-check to prevent the complicated schema validation afterwards.
honestly...I haven't got time to check the edge cases missing in v3 validator. I will do a quick spike in the subsequent PR.
| try { | ||
| await promisifiedValidator(spec, opts); | ||
| } catch (err) { | ||
| throw new Error(err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this suspicious. Are you wrapping err in a new Error object because it's a plain string? If that's the case, then you should add a check to avoid wrapping Error instances (e.g. TypeError) and losing the original stack trace.
} catch (err) {
if (err instanceof Error) {
throw err;
} else {
throw new Error(err);
}
}Eventually, we should fix the upstream module swagger2openapi to correctly throw Error instances instead of string error messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully all instances of this should have been fixed in swagger2openapi some time ago, but just a heads-up that in the upcoming v4.0.0 release, swagger2openapi will declare, export and throw a S2OError class so you will be able to identify intentionally thrown errors from any other problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @MikeRalphson for chiming in. We have moved away from swagger2openapi and use oas-validator instead, see https://github.com/Mermade/oas-kit/tree/master/packages/oas-validator and https://www.npmjs.com/package/oas-validator
Unfortunately, it seems that we are still rewrapping errors in a new Error object :(
| expect(params[7].schema.type).to.eql('string'); | ||
| expect(params[7].schema.format).to.eql('date-time'); | ||
| expect(params[8].schema.type).to.eql('string'); | ||
| expect(params[8].schema.format).to.eql('password'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, one logical assertion per test please.
On the second thought, it may be better to split this work in two pull requests: the first one copying openapi-v2 to openapi-v3 and perhaps changing module name in all places like package.json and license headers, the second PR making the actual upgrade. That way we can review all changes in the second pull request and still see only the relevant upgrade. The downside of my proposed approach is that Are there any other downsides to consider? Please do speak up if my proposed splitting is not practical! |
This is also my concern, while ideally....there shouldn't be any new features going into |
I was talking with Raymond about the same thing yesterday 😐 this PR is more like a PoC, the tricky thing for OpenAPI upgrade is, any partial upgrade of the spec cannot check in until all the specs are upgraded to generate a complete 3.0.0 spec file.
He is suggesting a similar way to break it down. This is also good for us to discuss a proper name for new packages(or old package if we hold v2 and v3 in one package but different folders) |
|
@bajtos Thank you for the review effort and all the feedbacks! types(PR merged)
break down
|
|
@jannyHou Are we good to close this PR now? |
|
All the related PRs are landed 🚢 I am closing this prototype PR, thanks again for all the review and suggestions! |
|
Great efforts! |
connect to #753
Checklist
npm testpasses on your machinepackages/cliwere updatedpackages/example-*were updatedHere is some quick notes for review this PR, file changes are huge, but the main change is in:
openapi-v3rest/src/parser.tsThe other ones are just the api upgrade from v2-->v3 and dependency change in
package.json.Writing more details for migration doc but it would not block the review.
Migration guide
https://github.com/strongloop/loopback-next/pull/916/files#diff-c1ba763eefca769778124b8f1c5c6b3f
Package Rename
openapi-spec--->openapi-spec-typesopenapi-v2--->openapi-v3New code
response spec
server spec
schema spec
openapi-v3controller-specinto 6 files@param@requestBodynew validator
build arguments
Discussion
OAS3SchemaObjectopenapi-spec-builderandopenapi-v3?