Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -24,7 +25,12 @@ export class Parameters {
public async compileTypes(opts: TypeCompilationOptions): Promise<string> {
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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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),
),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
);
},
);
}
Expand Down
7 changes: 6 additions & 1 deletion packages/generator/src/generation/model/paths/Path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string> {
Expand Down
81 changes: 79 additions & 2 deletions packages/generator/src/generation/populateNullableTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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;
}