diff --git a/README.md b/README.md index 00260b4..faf8790 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,8 @@ Remove the `webhooks` object, if present. ### ⤓ JSON Schema related changes -OAS 3.0 uses an earlier JSON Schema version (Draft 7). The tool converts `examples` +OAS 3.0 uses an earlier JSON Schema version +([JSON Schema Specification Wright Draft 00](https://datatracker.ietf.org/doc/html/draft-wright-json-schema-00)). The tool converts `examples` in schemas to a single `example`. As a special case, if the resulting `example` includes an `id`, it is @@ -340,7 +341,6 @@ becomes title: My Response description: Response from an API operation type: object - unevaluatedProperties: false allOf: ... ``` diff --git a/package-lock.json b/package-lock.json index 6fc96f4..fc1a73c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apiture/openapi-down-convert", - "version": "0.13.1", + "version": "0.13.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@apiture/openapi-down-convert", - "version": "0.13.1", + "version": "0.13.2", "license": "ISC", "dependencies": { "commander": "^9.4.1", diff --git a/package.json b/package.json index e9b647b..75bb310 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apiture/openapi-down-convert", - "version": "0.13.1", + "version": "0.13.2", "description": "Tool to down convert OpenAPI 3.1 to OpenAPI 3.0", "main": "lib/src/index.js", "bin": { diff --git a/src/converter.ts b/src/converter.ts index 07c3358..84b6c82 100644 --- a/src/converter.ts +++ b/src/converter.ts @@ -370,7 +370,8 @@ export class Converter { private json(x) { return JSON.stringify(x, null, 2); } - + /** HTTP methods */ + static readonly HTTP_METHODS = ['delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace' ]; /** * OpenAPI 3.1 defines a new `openIdConnect` security scheme. * Down-convert the scheme to `oauth2` / authorization code flow. @@ -383,12 +384,11 @@ export class Converter { const scopes = {}; const paths = this.openapi30?.paths; for (const path in paths) { - for (const op in paths[path]) { - if (op === 'parameters') { - continue; - } - const operation = paths[path][op]; - const sec = operation?.security as object[]; + // filter out path.{$ref, summary, description, parameters, servers} and x-* specification extensions + const methods = Object.keys(paths[path]).filter((op) => Converter.HTTP_METHODS.includes(op)); + methods.forEach(method => { + const operation = paths[path][method]; + const sec = (operation?.security || []) as object[]; sec.forEach((s) => { const requirement = s?.[schemeName] as string[]; if (requirement) { @@ -397,7 +397,7 @@ export class Converter { }); } }); - } + }); } return scopes; }; diff --git a/test/converter.spec.ts b/test/converter.spec.ts index 0b2f582..915eb57 100644 --- a/test/converter.spec.ts +++ b/test/converter.spec.ts @@ -773,6 +773,8 @@ describe('resolver test suite', () => { const scopes = converted.components.securitySchemes.accessToken.flows.authorizationCode.scopes; expect(scopes['scope1']).toEqual('Allow the application to access your personal profile data.'); expect(scopes['scope3']).toEqual(`TODO: describe the 'scope3' scope`); + const publicOp = (converted.paths['/users/{appId}/public-preferences'] as object)['get']; + expect(publicOp['security']).toBeFalsy(); done(); }); }); @@ -950,4 +952,3 @@ test('contentMediaType with existing unexpected format', (done) => { // TODO how to check that Converter logged to console.warn ? done(); }); - diff --git a/test/data/openapi.yaml b/test/data/openapi.yaml index e8f77a2..c209bdb 100644 --- a/test/data/openapi.yaml +++ b/test/data/openapi.yaml @@ -1,6 +1,6 @@ openapi: 3.1.0 info: - title: Transactions + title: Application Preferences (Example OpenAPI) description: ... version: 0.1.2 contact: @@ -18,10 +18,12 @@ tags: description: Application Preferences paths: /users/{appId}/preferences: + summary: Application preferences + description: A user's preferences for an application parameters: - $ref: '#/components/parameters/appIdPathParam' get: - summary: Return preferences for a application + summary: Return preferences for an application description: ... operationId: listPreferences tags: @@ -66,7 +68,7 @@ paths: application/pdf: schema: type: string - contentMediaType: application/json + contentMediaType: application/pdf contentEncoding: base64 maxLength: 5000000 '400': @@ -92,6 +94,58 @@ paths: - scope2 - scope3 - scope4 + /users/{appId}/public-preferences: + parameters: + - $ref: '#/components/parameters/appIdPathParam' + get: + summary: Return public preferences for an application, without auth + description: ... + operationId: listPublicPreferences + tags: + - Preferences + parameters: + - name: categories + description: >- + Filter preferences to only those whose `category` is in this + pipe-separated list. + in: query + style: pipeDelimited + schema: + type: array + minItems: 1 + maxItems: 16 + examples: + - - Presentation + - - Presentation + - Notifications + items: + type: string + - name: type + description: >- + Filter preferences only those whose `type` is in this pipe-separated + list. + in: query + style: pipeDelimited + schema: + type: array + minItems: 1 + maxItems: 4 + uniqueItems: true + items: + type: string + responses: + '200': + description: OK. + content: + application/json: + schema: + $ref: '#/components/schemas/preferences' + application/pdf: + schema: + type: string + contentMediaType: application/pdf + contentEncoding: base64 + maxLength: 5000000 components: securitySchemes: accessToken: @@ -437,4 +491,4 @@ components: minlength is for no milliseconds, such as '2021-10-30T19:06:00Z' maxLength is for '.' plus up to 9 digits for milliseconds, - such as '2021-10-30T19:06:04.999000999Z' \ No newline at end of file + such as '2021-10-30T19:06:04.999000999Z'