diff --git a/.changeset/neat-spiders-change.md b/.changeset/neat-spiders-change.md new file mode 100644 index 000000000..c386691a4 --- /dev/null +++ b/.changeset/neat-spiders-change.md @@ -0,0 +1,6 @@ +--- +"@logto/integration-tests": patch +"@logto/core": patch +--- + +fix incorrect swagger components diff --git a/packages/core/package.json b/packages/core/package.json index a95982ec5..18e4236b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -77,6 +77,7 @@ "otplib": "^12.0.1", "p-retry": "^6.0.0", "pg-protocol": "^1.6.0", + "pluralize": "^8.0.0", "qrcode": "^1.5.3", "redis": "^4.6.5", "roarr": "^7.11.0", @@ -105,6 +106,7 @@ "@types/koa__cors": "^4.0.0", "@types/node": "^18.11.18", "@types/oidc-provider": "^8.0.0", + "@types/pluralize": "^0.0.33", "@types/qrcode": "^1.5.2", "@types/semver": "^7.3.12", "@types/sinon": "^10.0.13", @@ -116,7 +118,7 @@ "nock": "^13.3.1", "node-mocks-http": "^1.12.1", "nodemon": "^3.0.0", - "openapi-types": "^12.0.0", + "openapi-types": "^12.1.3", "prettier": "^3.0.0", "sinon": "^17.0.0", "supertest": "^6.2.2", diff --git a/packages/core/src/routes/swagger/index.test.ts b/packages/core/src/routes/swagger/index.test.ts index 36fc60ab9..eb17eb5ea 100644 --- a/packages/core/src/routes/swagger/index.test.ts +++ b/packages/core/src/routes/swagger/index.test.ts @@ -97,6 +97,17 @@ describe('GET /swagger.json', () => { }), () => ({}) ); + // Test plural + queryParametersRouter.get( + '/mocks/:id/:field', + koaGuard({ + params: object({ + id: number(), + field: string(), + }), + }), + () => ({}) + ); const swaggerRequest = createSwaggerRequest([queryParametersRouter]); const response = await swaggerRequest.get('/swagger.json'); @@ -105,7 +116,22 @@ describe('GET /swagger.json', () => { get: { parameters: [ { - $ref: '#/components/parameters/mocId:root', + $ref: '#/components/parameters/mockId:root', + }, + { + name: 'field', + in: 'path', + required: true, + schema: { type: 'string' }, + }, + ], + }, + }, + '/api/mocks/{id}/{field}': { + get: { + parameters: [ + { + $ref: '#/components/parameters/mockId:root', }, { name: 'field', diff --git a/packages/core/src/routes/swagger/index.ts b/packages/core/src/routes/swagger/index.ts index 7ae730197..0a63ae0bd 100644 --- a/packages/core/src/routes/swagger/index.ts +++ b/packages/core/src/routes/swagger/index.ts @@ -104,8 +104,12 @@ const isManagementApiRouter = ({ stack }: Router) => // Add more components here to cover more ID parameters in paths. For example, if there is a // path `/foo/:barBazId`, then add `bar-baz` to the array. const identifiableEntityNames = [ + 'key', + 'connector-factory', + 'factory', 'application', 'connector', + 'sso-connector', 'resource', 'user', 'log', @@ -113,6 +117,7 @@ const identifiableEntityNames = [ 'scope', 'hook', 'domain', + 'verification', 'organization', 'organization-role', 'organization-scope', diff --git a/packages/core/src/routes/swagger/utils/parameters.ts b/packages/core/src/routes/swagger/utils/parameters.ts index 45cf5167d..587cecdf5 100644 --- a/packages/core/src/routes/swagger/utils/parameters.ts +++ b/packages/core/src/routes/swagger/utils/parameters.ts @@ -1,6 +1,7 @@ import camelcase from 'camelcase'; import deepmerge from 'deepmerge'; import { type OpenAPIV3 } from 'openapi-types'; +import pluralize from 'pluralize'; import { z } from 'zod'; import { fallbackDefaultPageSize } from '#src/middleware/koa-pagination.js'; @@ -87,7 +88,7 @@ export const buildParameters: BuildParameters = ( if (key === 'id') { if (rootComponent) { return { - $ref: `#/components/parameters/${rootComponent.slice(0, -1)}Id:root`, + $ref: `#/components/parameters/${pluralize(rootComponent, 1)}Id:root`, }; } diff --git a/packages/core/src/utils/zod.ts b/packages/core/src/utils/zod.ts index 231eeb003..901550715 100644 --- a/packages/core/src/utils/zod.ts +++ b/packages/core/src/utils/zod.ts @@ -49,9 +49,11 @@ export const translationSchemas: Record = { { type: 'string', }, - { - $ref: '#/components/schemas/Translation', - }, + // { + // // This self-reference is OK, but it's not supported by Swagger UI + // // See https://github.com/swagger-api/swagger-ui/issues/3325 + // $ref: '#/components/schemas/TranslationObject', + // }, ], }, }; diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 8f0381dbc..4a30a0595 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -21,6 +21,7 @@ "start": "pnpm test" }, "devDependencies": { + "@apidevtools/swagger-parser": "^10.1.0", "@jest/test-sequencer": "^29.5.0", "@jest/types": "^29.1.2", "@logto/connector-kit": "workspace:^2.0.0", @@ -42,8 +43,8 @@ "jest-puppeteer": "^9.0.0", "jose": "^5.0.0", "node-fetch": "^3.3.0", - "openapi-schema-validator": "^12.0.0", - "openapi-types": "^12.0.0", + "openapi-schema-validator": "^12.1.3", + "openapi-types": "^12.1.3", "prettier": "^3.0.0", "puppeteer": "^21.0.0", "text-encoder": "^0.0.4", diff --git a/packages/integration-tests/src/tests/api/swagger-check.test.ts b/packages/integration-tests/src/tests/api/swagger-check.test.ts index cb25c7d3a..c4d857934 100644 --- a/packages/integration-tests/src/tests/api/swagger-check.test.ts +++ b/packages/integration-tests/src/tests/api/swagger-check.test.ts @@ -1,3 +1,4 @@ +import * as SwaggerParser from '@apidevtools/swagger-parser'; import Validator from 'openapi-schema-validator'; import type { OpenAPI } from 'openapi-types'; @@ -11,11 +12,17 @@ describe('Swagger check', () => { expect(response).toHaveProperty('statusCode', 200); expect(response.headers['content-type']).toContain('application/json'); - expect(() => { + // Use multiple validators to be more confident + expect(async () => { const object: unknown = JSON.parse(response.body); + const validator = new OpenApiSchemaValidator({ version: 3 }); const result = validator.validate(object as OpenAPI.Document); expect(result.errors).toEqual([]); + + await expect( + SwaggerParser.default.validate(object as OpenAPI.Document) + ).resolves.not.toThrow(); }).not.toThrow(); }); }); diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 9ed2d1eee..ba824ac73 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -46,7 +46,7 @@ "@types/inquirer": "^9.0.0", "@types/jest": "^29.4.0", "@types/node": "^18.11.18", - "@types/pluralize": "^0.0.32", + "@types/pluralize": "^0.0.33", "camelcase": "^8.0.0", "chalk": "^5.0.0", "eslint": "^8.44.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3091b7c39..d2ebdabc4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3280,6 +3280,9 @@ importers: pg-protocol: specifier: ^1.6.0 version: 1.6.0 + pluralize: + specifier: ^8.0.0 + version: 8.0.0 qrcode: specifier: ^1.5.3 version: 1.5.3 @@ -3359,6 +3362,9 @@ importers: '@types/oidc-provider': specifier: ^8.0.0 version: 8.0.0 + '@types/pluralize': + specifier: ^0.0.33 + version: 0.0.33 '@types/qrcode': specifier: ^1.5.2 version: 1.5.2 @@ -3393,8 +3399,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 openapi-types: - specifier: ^12.0.0 - version: 12.0.0 + specifier: ^12.1.3 + version: 12.1.3 prettier: specifier: ^3.0.0 version: 3.0.0 @@ -3720,6 +3726,9 @@ importers: specifier: ^12.0.1 version: 12.0.1 devDependencies: + '@apidevtools/swagger-parser': + specifier: ^10.1.0 + version: 10.1.0(openapi-types@12.1.3) '@jest/test-sequencer': specifier: ^29.5.0 version: 29.5.0 @@ -3784,11 +3793,11 @@ importers: specifier: ^3.3.0 version: 3.3.0 openapi-schema-validator: - specifier: ^12.0.0 - version: 12.0.0 + specifier: ^12.1.3 + version: 12.1.3 openapi-types: - specifier: ^12.0.0 - version: 12.0.0 + specifier: ^12.1.3 + version: 12.1.3 prettier: specifier: ^3.0.0 version: 3.0.0 @@ -3916,8 +3925,8 @@ importers: specifier: ^18.11.18 version: 18.11.18 '@types/pluralize': - specifier: ^0.0.32 - version: 0.0.32 + specifier: ^0.0.33 + version: 0.0.33 camelcase: specifier: ^8.0.0 version: 8.0.0 @@ -4177,6 +4186,38 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true + /@apidevtools/json-schema-ref-parser@9.0.6: + resolution: {integrity: sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==} + dependencies: + '@jsdevtools/ono': 7.1.3 + call-me-maybe: 1.0.2 + js-yaml: 3.14.1 + dev: true + + /@apidevtools/openapi-schemas@2.1.0: + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + dev: true + + /@apidevtools/swagger-methods@3.0.2: + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + dev: true + + /@apidevtools/swagger-parser@10.1.0(openapi-types@12.1.3): + resolution: {integrity: sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==} + peerDependencies: + openapi-types: '>=7' + dependencies: + '@apidevtools/json-schema-ref-parser': 9.0.6 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + ajv: 8.12.0 + ajv-draft-04: 1.0.0(ajv@8.12.0) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + dev: true + /@authenio/samlify-node-xmllint@2.0.0(samlify@2.8.10): resolution: {integrity: sha512-V9cQ0CHqu3JwOmbSecGPUnzIES5kHxD00FEZKnWh90ksQUJG5/TscV2r9XLbKp7MlRMOSUfWxecM35xPSLFdSg==} peerDependencies: @@ -7345,6 +7386,10 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@jsdevtools/ono@7.1.3: + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + dev: true + /@koa/cors@4.0.0: resolution: {integrity: sha512-Y4RrbvGTlAaa04DBoPBWJqDR5gPj32OOz827ULXfgB1F7piD1MB/zwn8JR2LAnvdILhxUbXbkXGWuNVsFuVFCQ==} engines: {node: '>= 14.0.0'} @@ -9965,8 +10010,8 @@ packages: pg-types: 2.2.0 dev: true - /@types/pluralize@0.0.32: - resolution: {integrity: sha512-exDkoRIkWJlbRDRmtYDbI3ZUE28HwBwHe5VKn4mvpvMW7qIRDHO6URItErBsBSX7J8/PrDLSOHCcbUMFXwA6CA==} + /@types/pluralize@0.0.33: + resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==} dev: true /@types/prettier@2.7.1: @@ -10406,7 +10451,18 @@ packages: transitivePeerDependencies: - supports-color - /ajv-formats@2.1.1(ajv@8.8.2): + /ajv-draft-04@1.0.0(ajv@8.12.0): + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + dev: true + + /ajv-formats@2.1.1(ajv@8.12.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: ajv: ^8.0.0 @@ -10414,7 +10470,7 @@ packages: ajv: optional: true dependencies: - ajv: 8.8.2 + ajv: 8.12.0 dev: true /ajv@6.12.6: @@ -10434,15 +10490,6 @@ packages: uri-js: 4.4.1 dev: true - /ajv@8.8.2: - resolution: {integrity: sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - dev: true - /ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -11011,6 +11058,10 @@ packages: function-bind: 1.1.1 get-intrinsic: 1.1.3 + /call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + dev: true + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -17016,17 +17067,17 @@ packages: is-wsl: 2.2.0 dev: true - /openapi-schema-validator@12.0.0: - resolution: {integrity: sha512-dtQ5iCiCluL/SXmd5LWqfPXvN/WbVJeCp+3+exF6BHBN3fry5tNAhFllZICYHQ2kTfQxrfwbQcy0fqaw9wOb+w==} + /openapi-schema-validator@12.1.3: + resolution: {integrity: sha512-xTHOmxU/VQGUgo7Cm0jhwbklOKobXby+/237EG967+3TQEYJztMgX9Q5UE2taZKwyKPUq0j11dngpGjUuxz1hQ==} dependencies: - ajv: 8.8.2 - ajv-formats: 2.1.1(ajv@8.8.2) + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) lodash.merge: 4.6.2 - openapi-types: 12.0.0 + openapi-types: 12.1.3 dev: true - /openapi-types@12.0.0: - resolution: {integrity: sha512-6Wd9k8nmGQHgCbehZCP6wwWcfXcvinhybUTBatuhjRsCxUIujuYFZc9QnGeae75CyHASewBtxs0HX/qwREReUw==} + /openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} dev: true /optionator@0.8.3: @@ -17524,7 +17575,6 @@ packages: /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - dev: true /pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}