Skip to content

Commit

Permalink
feat: upstream v5 support
Browse files Browse the repository at this point in the history
Adding support for the v5 of the upstream dependency.

BREAKING CHANGE: types have been changed it appears for openapi version
3.1.0 if passed in as the argument. Paths is now optional on the
  returned object.
  • Loading branch information
vacas5 committed Nov 25, 2024
1 parent 78dbed2 commit b7da137
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 29 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "NODE_ENV=test mocha --exit"
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "^4.8.0"
"@asteasolutions/zod-to-openapi": "^5.5.0"
},
"peerDependencies": {
"express": "^5.0.0-beta.1",
Expand Down
8 changes: 4 additions & 4 deletions src/openAPI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("buildOpenAPIDocument", () => {
const document = buildOpenAPIDocument({ config, routers, schemaPaths, errors, openApiVersion });

expect(document.paths).to.have.property("/test");
expect(document.paths["/test"]).to.have.property("get");
expect(document.paths!["/test"]).to.have.property("get");
});

it("should include error responses if defined", () => {
Expand Down Expand Up @@ -160,8 +160,8 @@ describe("buildOpenAPIDocument", () => {
const errors = { 401: "Unauthorized", 403: "Forbidden" };

const document = buildOpenAPIDocument({ config, routers, schemaPaths, errors, openApiVersion });
const method = document.paths["/test"].get;
const responseSchema = method!.responses["200"].content["application/json"].schema;
const method = document.paths!["/test"].get;
const responseSchema = method!.responses!["200"].content["application/json"].schema;

expect(responseSchema.$ref.includes("ResponseSchema")).to.be.true;
});
Expand All @@ -186,7 +186,7 @@ describe("buildOpenAPIDocument", () => {
const errors = { 401: "Unauthorized", 403: "Forbidden" };

const document = buildOpenAPIDocument({ config, routers, schemaPaths, errors, openApiVersion });
const method = document.paths["/test"].get;
const method = document.paths!["/test"].get;
// @ts-ignore
const requestBodySchema = method!.requestBody?.content["application/json"].schema;

Expand Down
47 changes: 27 additions & 20 deletions src/openAPI.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
extendZodWithOpenApi,
OpenAPIGenerator,
OpenApiGeneratorV3,
OpenApiGeneratorV31,
OpenAPIRegistry,
ResponseConfig,
RouteConfig,
} from "@asteasolutions/zod-to-openapi";
import { OpenApiVersion } from "@asteasolutions/zod-to-openapi/dist/openapi-generator";
import { RequestHandler, Router } from "express";
import type { ComponentsObject } from "openapi3-ts/oas30";
import { z, ZodArray, ZodEffects, ZodObject } from "zod";
Expand All @@ -13,19 +15,19 @@ import { ErrorResponse } from "./schemas";

extendZodWithOpenApi(z);

export type OpenAPIDocument = ReturnType<OpenAPIGenerator["generateDocument"]>;
export type OpenAPIComponents = ReturnType<OpenAPIGenerator["generateComponents"]>;
export type OpenAPIConfig = Parameters<OpenAPIGenerator["generateDocument"]>[0];
export type OpenApiVersion = ConstructorParameters<typeof OpenAPIGenerator>[1];
export type OpenAPIDocument = ReturnType<OpenApiGeneratorV3["generateDocument"]>;
export type OpenAPIV31Document = ReturnType<OpenApiGeneratorV31["generateDocument"]>;
export type OpenAPIComponents = ReturnType<OpenApiGeneratorV3["generateComponents"]>;
export type OpenAPIConfig = Parameters<OpenApiGeneratorV3["generateDocument"]>[0];

export function buildOpenAPIDocument(args: {
config: OpenAPIConfig;
config: Omit<OpenAPIConfig, "openapi">;
routers: Router[];
schemaPaths: string[];
errors: { 401?: string; 403?: string };
securitySchemes?: ComponentsObject["securitySchemes"];
openApiVersion: OpenApiVersion;
}): OpenAPIDocument {
}): OpenAPIDocument | OpenAPIV31Document {
const { config, routers, schemaPaths, securitySchemes, errors, openApiVersion } = args;
const registry = new OpenAPIRegistry();
// Attach all of the Zod schemas to the OpenAPI specification
Expand Down Expand Up @@ -189,8 +191,11 @@ export function buildOpenAPIDocument(args: {
registry.registerPath(openapiRouteConfig);
});

const generator = new OpenAPIGenerator(registry.definitions, openApiVersion);
const openapiJSON = generator.generateDocument(config);
const generator =
openApiVersion === "3.1.0"
? new OpenApiGeneratorV31(registry.definitions)
: new OpenApiGeneratorV3(registry.definitions);
const openapiJSON = generator.generateDocument({ ...config, openapi: openApiVersion });

// Attach the security schemes provided
if (securitySchemes) {
Expand All @@ -200,17 +205,19 @@ export function buildOpenAPIDocument(args: {

// Verify that none of the "parameters" are appearing as optional, which is invalid
// in the official OpenAPI spec and unsupported by readme.io
for (const [route, impl] of Object.entries(openapiJSON.paths)) {
for (const key of Object.keys(impl)) {
const method = key as keyof typeof impl;
for (const param of impl[method].parameters || []) {
if (param.required === false && param.in === "path") {
param.required = true;
console.warn(
`OpenAPI Warning: The route ${route} has an optional parameter ${param.name} in the path. ` +
`Optional parameters in the route path are not supported by readme.io. Make the parameter required ` +
`or split the route definition into two separate ones, one with the param and one without.`,
);
if (openapiJSON.paths) {
for (const [route, impl] of Object.entries(openapiJSON.paths)) {
for (const key of Object.keys(impl)) {
const method = key as keyof typeof impl;
for (const param of impl[method].parameters || []) {
if (param.required === false && param.in === "path") {
param.required = true;
console.warn(
`OpenAPI Warning: The route ${route} has an optional parameter ${param.name} in the path. ` +
`Optional parameters in the route path are not supported by readme.io. Make the parameter required ` +
`or split the route definition into two separate ones, one with the param and one without.`,
);
}
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
# yarn lockfile v1


"@asteasolutions/zod-to-openapi@^4.8.0":
version "4.8.0"
resolved "https://registry.yarnpkg.com/@asteasolutions/zod-to-openapi/-/zod-to-openapi-4.8.0.tgz#34c29228c5cd0098a534b3536c9f486fafca2cc1"
integrity sha512-FQlBeHIhSoEhjddAWD1v2zbeBESfTdaAVQuUr0RY9t0rN8ogoEaGirv2Ne8kQQTAVGydJzUMAOIlPULEOQDoeA==
"@asteasolutions/zod-to-openapi@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@asteasolutions/zod-to-openapi/-/zod-to-openapi-5.5.0.tgz#f3be8f1e97ece0fcef2e6ff5e9230da8138d20a6"
integrity sha512-d5HwrvM6dOKr3XdeF+DmashGvfEc+1oiEfbscugsiwSTrFtuMa7ETpW9sTNnVgn+hJaz+PRxPQUYD7q9/5dUig==
dependencies:
openapi3-ts "^4.1.2"

Expand Down

0 comments on commit b7da137

Please sign in to comment.