diff --git a/packages/generator/src/generation/model/components/Parameters.ts b/packages/generator/src/generation/model/components/Parameters.ts index 0b6f3581..3d58edee 100644 --- a/packages/generator/src/generation/model/components/Parameters.ts +++ b/packages/generator/src/generation/model/components/Parameters.ts @@ -4,6 +4,7 @@ import { Components, ComponentType } from "./Components.js"; import { asyncStringJoin } from "../../asyncStringJoin.js"; import { TypeCompilationOptions } from "../CodeGenerationModel.js"; import { assertNoRefs } from "../../refs/assertNoRefs.js"; +import { populateNullableTypes } from "../../populateNullableTypes.js"; export class Parameters { public static readonly ns = "Parameters"; @@ -24,7 +25,12 @@ export class Parameters { public async compileTypes(opts: TypeCompilationOptions): Promise { const schemas = Object.entries(this.parameters).map(([name, param]) => { assertNoRefs(param); - return new JSONSchema(new Name(name, this.name), param.schema); + return new JSONSchema( + new Name(name, this.name), + param.schema !== undefined + ? populateNullableTypes(param.schema) + : undefined, + ); }); const t = { diff --git a/packages/generator/src/generation/model/components/RequestBodies.ts b/packages/generator/src/generation/model/components/RequestBodies.ts index ccb1c8f0..f412ee65 100644 --- a/packages/generator/src/generation/model/components/RequestBodies.ts +++ b/packages/generator/src/generation/model/components/RequestBodies.ts @@ -4,6 +4,7 @@ import { Components } from "./Components.js"; import { asyncStringJoin } from "../../asyncStringJoin.js"; import { TypeCompilationOptions } from "../CodeGenerationModel.js"; import { OpenAPIV3 } from "openapi-types"; +import { populateNullableTypesForRequestBody } from "../../populateNullableTypes.js"; export class RequestBodies { public static readonly ns = "RequestBodies"; @@ -20,7 +21,10 @@ export class RequestBodies { this.name = new Name(RequestBodies.ns, components.name); this.schemas = Object.entries(schemas ?? {}).map( ([schemaName, schema]) => - new JSONSchema(new Name(schemaName, this.name), schema), + new JSONSchema( + new Name(schemaName, this.name), + populateNullableTypesForRequestBody(schema), + ), ); } diff --git a/packages/generator/src/generation/model/components/Response.ts b/packages/generator/src/generation/model/components/Response.ts index 6f7c5c41..5a0b4a69 100644 --- a/packages/generator/src/generation/model/components/Response.ts +++ b/packages/generator/src/generation/model/components/Response.ts @@ -5,6 +5,7 @@ import { Responses } from "./Responses.js"; import { TypeCompilationOptions } from "../CodeGenerationModel.js"; import { OpenAPIV3 } from "openapi-types"; import invariant from "invariant"; +import { populateNullableTypes } from "../../populateNullableTypes.js"; export class Response { public readonly name: Name; @@ -21,7 +22,13 @@ export class Response { this.contents = Object.entries(mediaTypesDoc).map( ([mediaType, mediaTypeObj]) => { invariant(!!mediaTypeObj?.schema, "No schema set"); - return new ResponseContent(this, mediaType, mediaTypeObj?.schema); + return new ResponseContent( + this, + mediaType, + mediaTypeObj?.schema + ? populateNullableTypes(mediaTypeObj.schema) + : mediaTypeObj.schema, + ); }, ); } diff --git a/packages/generator/src/generation/model/paths/Path.ts b/packages/generator/src/generation/model/paths/Path.ts index 848f870a..13061280 100644 --- a/packages/generator/src/generation/model/paths/Path.ts +++ b/packages/generator/src/generation/model/paths/Path.ts @@ -4,6 +4,7 @@ import { asyncStringJoin } from "../../asyncStringJoin.js"; import { Paths } from "./Paths.js"; import { TypeCompilationOptions } from "../CodeGenerationModel.js"; import { OpenAPIV3 } from "openapi-types"; +import { populateNullableTypesForPathItem } from "../../populateNullableTypes.js"; export class Path { public readonly paths: Paths; @@ -31,7 +32,11 @@ export class Path { name: string, operationsDoc: OpenAPIV3.PathItemObject, ) { - return new Path(paths, new Name(name, paths.name), operationsDoc); + return new Path( + paths, + new Name(name, paths.name), + populateNullableTypesForPathItem(operationsDoc), + ); } public async compileTypes(options: TypeCompilationOptions): Promise { diff --git a/packages/generator/src/generation/populateNullableTypes.ts b/packages/generator/src/generation/populateNullableTypes.ts index 46050a2e..da3fefcc 100644 --- a/packages/generator/src/generation/populateNullableTypes.ts +++ b/packages/generator/src/generation/populateNullableTypes.ts @@ -18,7 +18,6 @@ export function populateNullableTypes( } const compositionKeys = ["allOf", "anyOf", "oneOf"] as const; - compositionKeys.forEach((key) => { const entries = schema[key]; if (Array.isArray(entries)) { @@ -27,10 +26,88 @@ export function populateNullableTypes( }); if (schema.nullable) { + const { nullable: ignoredNullable, ...schemaWithoutNullable } = schema; return { - anyOf: [schema, { type: "null" } as unknown as OpenAPIV3.SchemaObject], + anyOf: [ + schemaWithoutNullable as OpenAPIV3.SchemaObject, + { type: "null" } as unknown as OpenAPIV3.SchemaObject, + ], }; } return schema; } + +export function populateNullableTypesForRequestBody( + requestBody: OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject, +): OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject { + if (!requestBody || "$ref" in requestBody) { + return requestBody; + } + for (const mediaType in requestBody.content) { + const mediaTypeObject = requestBody.content[mediaType]; + if (mediaTypeObject?.schema) { + mediaTypeObject.schema = populateNullableTypes(mediaTypeObject.schema); + } + } + return requestBody; +} + +export function populateNullableTypesForResponse( + response: OpenAPIV3.ReferenceObject | OpenAPIV3.ResponseObject, +): OpenAPIV3.ReferenceObject | OpenAPIV3.ResponseObject { + if (!response || "$ref" in response) { + return response; + } + if (response.content) { + for (const mediaType in response.content) { + const mediaTypeObject = response.content[mediaType]; + if (mediaTypeObject?.schema) { + mediaTypeObject.schema = populateNullableTypes(mediaTypeObject.schema); + } + } + } + return response; +} + +export function populateNullableTypesForPathItem( + pathItem: OpenAPIV3.ReferenceObject | OpenAPIV3.PathItemObject, +): OpenAPIV3.ReferenceObject | OpenAPIV3.PathItemObject { + if (!pathItem || "$ref" in pathItem) { + return pathItem; + } + + for (const httpMethod of Object.values(OpenAPIV3.HttpMethods)) { + const operation = pathItem[httpMethod]; + + if (!operation) { + continue; + } + + if (operation.parameters) { + operation.parameters.forEach((param) => { + if (param && !("$ref" in param) && param.schema) { + param.schema = populateNullableTypes(param.schema); + } + }); + } + + if (operation.requestBody) { + operation.requestBody = populateNullableTypesForRequestBody( + operation.requestBody, + ); + } + + if (operation.responses) { + for (const statusCode in operation.responses) { + const response = operation.responses[statusCode]; + if (response) { + operation.responses[statusCode] = + populateNullableTypesForResponse(response); + } + } + } + } + + return pathItem; +}