diff --git a/packages/type-safe-api/scripts/type-safe-api/parser/parse-openapi-spec.ts b/packages/type-safe-api/scripts/type-safe-api/parser/parse-openapi-spec.ts index 3986bd030..49003f2f8 100644 --- a/packages/type-safe-api/scripts/type-safe-api/parser/parse-openapi-spec.ts +++ b/packages/type-safe-api/scripts/type-safe-api/parser/parse-openapi-spec.ts @@ -4,7 +4,9 @@ import SwaggerParser from "@apidevtools/swagger-parser"; import { writeFile } from "projen/lib/util"; import { parse } from "ts-command-line-args"; import * as path from 'path'; +import * as _ from "lodash"; import fs from "fs"; +import type { OpenAPIV3 } from "openapi-types"; // Smithy HTTP trait is used to map Smithy operations to their location in the spec const SMITHY_HTTP_TRAIT_ID = "smithy.api#http"; @@ -49,6 +51,7 @@ interface Arguments { readonly outputPath: string; } + void (async () => { const args = parse({ specPath: { type: String, alias: "s" }, @@ -101,8 +104,16 @@ void (async () => { const invalidRequestParameters: InvalidRequestParameter[] = []; + // Dereference a clone of the spec to test parameters + const dereferencedSpec = await SwaggerParser.dereference(JSON.parse(JSON.stringify(spec)), { + dereference: { + // Circular references are valid, we just ignore them for the purpose of validation + circular: "ignore", + }, + }); + // Validate the request parameters - Object.entries(spec.paths || {}).forEach(([p, pathOp]: [string, any]) => { + Object.entries(dereferencedSpec.paths || {}).forEach(([p, pathOp]: [string, any]) => { Object.entries(pathOp ?? {}).forEach(([method, operation]: [string, any]) => { (operation?.parameters ?? []).forEach((parameter: any) => { // Check if the parameter is an allowed type diff --git a/packages/type-safe-api/test/resources/specs/parameter-refs.yaml b/packages/type-safe-api/test/resources/specs/parameter-refs.yaml new file mode 100644 index 000000000..377236f18 --- /dev/null +++ b/packages/type-safe-api/test/resources/specs/parameter-refs.yaml @@ -0,0 +1,39 @@ +openapi: 3.0.3 +info: + version: 1.0.0 + title: Example API +paths: + /hello: + get: + operationId: sayHello + x-handler: + language: typescript + parameters: + - $ref: '#/components/parameters/HelloId' + responses: + '200': + description: Successful response + content: + 'application/json': + schema: + $ref: '#/components/schemas/HelloResponse' +components: + parameters: + HelloId: + in: query + name: id + schema: + $ref: '#/components/schemas/HelloId' + required: false + schemas: + HelloId: + type: string + HelloResponse: + type: object + properties: + id: + $ref: '#/components/schemas/HelloId' + message: + $ref: '#/components/schemas/HelloResponse' + required: + - id \ No newline at end of file diff --git a/packages/type-safe-api/test/scripts/parser/__snapshots__/parse-openapi-spec.test.ts.snap b/packages/type-safe-api/test/scripts/parser/__snapshots__/parse-openapi-spec.test.ts.snap index 1697e4da5..75670fc6c 100644 --- a/packages/type-safe-api/test/scripts/parser/__snapshots__/parse-openapi-spec.test.ts.snap +++ b/packages/type-safe-api/test/scripts/parser/__snapshots__/parse-openapi-spec.test.ts.snap @@ -366,3 +366,73 @@ exports[`Parse OpenAPI Spec Script Unit Tests Injects @handler and @paginated tr }, } `; + +exports[`Parse OpenAPI Spec Script Unit Tests Permits parameter references (and circular references) 1`] = ` +{ + ".api.json": { + "components": { + "parameters": { + "HelloId": { + "in": "query", + "name": "id", + "required": false, + "schema": { + "$ref": "#/components/schemas/HelloId", + }, + }, + }, + "schemas": { + "HelloId": { + "type": "string", + }, + "HelloResponse": { + "properties": { + "id": { + "$ref": "#/components/schemas/HelloId", + }, + "message": { + "$ref": "#/components/schemas/HelloResponse", + }, + }, + "required": [ + "id", + ], + "type": "object", + }, + }, + }, + "info": { + "title": "Example API", + "version": "1.0.0", + }, + "openapi": "3.0.3", + "paths": { + "/hello": { + "get": { + "operationId": "sayHello", + "parameters": [ + { + "$ref": "#/components/parameters/HelloId", + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HelloResponse", + }, + }, + }, + "description": "Successful response", + }, + }, + "x-handler": { + "language": "typescript", + }, + }, + }, + }, + }, +} +`; diff --git a/packages/type-safe-api/test/scripts/parser/parse-openapi-spec.test.ts b/packages/type-safe-api/test/scripts/parser/parse-openapi-spec.test.ts index 44510a947..bffc94ea6 100644 --- a/packages/type-safe-api/test/scripts/parser/parse-openapi-spec.test.ts +++ b/packages/type-safe-api/test/scripts/parser/parse-openapi-spec.test.ts @@ -58,4 +58,20 @@ describe("Parse OpenAPI Spec Script Unit Tests", () => { ); }); }); + + it("Permits parameter references (and circular references)", () => { + expect( + withTmpDirSnapshot(os.tmpdir(), (tmpDir) => { + const specPath = "../../resources/specs/parameter-refs.yaml"; + const outputPath = path.join( + path.relative(path.resolve(__dirname), tmpDir), + ".api.json" + ); + const command = `../../../scripts/type-safe-api/parser/parse-openapi-spec --spec-path ${specPath} --output-path ${outputPath}`; + exec(command, { + cwd: path.resolve(__dirname), + }); + }) + ).toMatchSnapshot(); + }); });