diff --git a/package-lock.json b/package-lock.json index 17a6e90..8d12ff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,14 +18,6 @@ "@tsconfig/node22": "^22.0.2" } }, - "node_modules/@openapi-ts/backend": { - "resolved": "packages/lib", - "link": true - }, - "node_modules/@openapi-ts/cli": { - "resolved": "packages/cli", - "link": true - }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.9.3", "license": "MIT", @@ -1199,6 +1191,14 @@ "node": ">= 8" } }, + "node_modules/@openapi-ts/backend": { + "resolved": "packages/lib", + "link": true + }, + "node_modules/@openapi-ts/cli": { + "resolved": "packages/cli", + "link": true + }, "node_modules/@openapi-ts/request-types": { "version": "1.1.0", "dev": true, @@ -5261,7 +5261,7 @@ "name": "@openapi-ts/cli", "version": "3.0.1", "dependencies": { - "openapi-typescript": "^7.9.1" + "openapi-typescript": "https://github.com/luqasn/openapi-typescript/releases/download/v7.11.1-preview/openapi-typescript-7.11.1-preview.tgz" }, "bin": { "openapi-ts-backend": "dist/cli.js" @@ -5270,6 +5270,38 @@ "typescript": "^5.9.3" } }, + "packages/cli/node_modules/openapi-typescript": { + "version": "7.11.1-preview", + "resolved": "https://github.com/luqasn/openapi-typescript/releases/download/v7.11.1-preview/openapi-typescript-7.11.1-preview.tgz", + "integrity": "sha512-ktv+A9S0BzoGft4ugze1OAUFFwGg16ls1/ykdLfp0UZIckeTXmdW82G90ENg6EnhfiyMlnG9y6Uqo6SJ3f9D3A==", + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.5", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "packages/cli/node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "packages/lib": { "name": "@openapi-ts/backend", "version": "3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index d423f20..a07d792 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -10,7 +10,7 @@ "postbuild": "chmod u+x dist/cli.js" }, "dependencies": { - "openapi-typescript": "^7.9.1" + "openapi-typescript": "https://github.com/luqasn/openapi-typescript/releases/download/v7.11.1-preview/openapi-typescript-7.11.1-preview.tgz" }, "peerDependencies": { "typescript": "^5.9.3" diff --git a/packages/cli/src/typegen/typegen.ts b/packages/cli/src/typegen/typegen.ts index ac74c84..db27b4c 100644 --- a/packages/cli/src/typegen/typegen.ts +++ b/packages/cli/src/typegen/typegen.ts @@ -17,7 +17,10 @@ function write(dirName: string, fileName: string, data: string) { async function createSpecTypes(specPath: string, outputDir: string) { const absolutePath = path.resolve(specPath); const url = pathToFileURL(absolutePath) - const ast = await openapiTS(url); + + const ast = await openapiTS(url, { + makeParametersWithDefaultNotUndefined: true + }); const ts = astToString(ast); return write(outputDir, 'spec.ts', ts); diff --git a/packages/lib/src/openapi.ts b/packages/lib/src/openapi.ts index 945e287..cba56aa 100644 --- a/packages/lib/src/openapi.ts +++ b/packages/lib/src/openapi.ts @@ -137,16 +137,34 @@ export class OpenApi { return api; } + protected injectDefaults(parsed: Params, parameterMap: Record): Params { + for (const [name, p] of Object.entries(parameterMap)) { + if (p.schema === undefined) { + continue + } + if ("$ref" in p.schema) { + throw new Error('this should never happen, schema should be dereferenced by openapi-backend') + } + if (p.schema.default !== undefined && parsed[name] === undefined) { + parsed[name] = p.schema.default + } + } + return parsed; + } + protected parseParams(rawParams: StringParams, operation: OpenAPI.Operation, type: ParameterType, errors: Ajv.ErrorObject[]): Params { + const parameterMap = getParameterMap(operation, type) // This is mostly used to coerce types, which openapi-backend does internally but then throws away - return matchSchema( + const parsed = matchSchema( this.paramValidator, rawParams, - getParametersSchema(getParameterMap(operation, type)), + getParametersSchema(parameterMap), errors); + + return this.injectDefaults(parsed, parameterMap) } protected parseRequest(apiContext: OpenAPI.Context): Request { diff --git a/packages/test/api.yml b/packages/test/api.yml index 312dd91..ace7cc5 100644 --- a/packages/test/api.yml +++ b/packages/test/api.yml @@ -20,6 +20,7 @@ components: schemas: Title: type: string + default: Mrs enum: - Mr - Mrs diff --git a/packages/test/src/openapi.test.ts b/packages/test/src/openapi.test.ts index c822293..1f059d4 100644 --- a/packages/test/src/openapi.test.ts +++ b/packages/test/src/openapi.test.ts @@ -11,7 +11,7 @@ function getTypeMap(obj: any) { const operations: OperationHandlers = { greet: req => { - const {params: {name}, query: {title = ''}} = req; + const {params: {name}, query: {title}} = req; return { message: greet(title, name), @@ -61,6 +61,19 @@ describe('API tests', () => { } }); expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({message: 'Hello, Mr John Doe'}) + }); + + it('Should set default values for parameters automatically', async () => { + const res = await api.handleRequest({ + method: 'GET', + path: '/greet/Jane%20Doe', + headers: { + authorization: 'true', + } + }); + expect(res.statusCode).toEqual(200); + expect(res.body).toEqual({message: 'Hello, Mrs Jane Doe'}) }); it('Should handle a valid request with implicit status 201', async () => {