1
1
import { OpenApiGeneratorV31 , OpenAPIRegistry , RouteConfig } from "@asteasolutions/zod-to-openapi" ;
2
- import { AnyZodObject , ZodType } from "zod" ;
3
- import type { InfoObject , OpenAPIObject , ServerObject } from "openapi3-ts/oas31" ;
2
+ import { AnyZodObject , z , ZodType } from "zod" ;
3
+ import type { ExampleObject , InfoObject , OpenAPIObject , ServerObject } from "openapi3-ts/oas31" ;
4
4
import Config from "./config" ;
5
5
import { ResponsesObject , Specification } from "../middleware/specification" ;
6
6
import { SwaggerUiOptions } from "swagger-ui-express" ;
@@ -60,6 +60,65 @@ export function getOpenAPISpec(): OpenAPIObject {
60
60
return openAPISpec ;
61
61
}
62
62
63
+ // Given a specification, returns the route responses in openapi format
64
+ function getPathResponsesForSpecification <
65
+ Params extends AnyZodObject ,
66
+ Query extends AnyZodObject ,
67
+ Responses extends ResponsesObject ,
68
+ Body extends ZodType ,
69
+ > ( specification : Specification < Params , Query , Responses , Body > ) : RouteConfig [ "responses" ] {
70
+ const responses : RouteConfig [ "responses" ] = { } ;
71
+
72
+ for ( const [ statusCode , response ] of Object . entries ( specification . responses ) ) {
73
+ // response can be a single response or an array of responses for this status code
74
+ // First, check for the easy singular case
75
+ if ( ! Array . isArray ( response ) ) {
76
+ const { description, schema } = response ;
77
+ responses [ statusCode ] = {
78
+ description,
79
+ content : {
80
+ "application/json" : {
81
+ schema,
82
+ } ,
83
+ } ,
84
+ } ;
85
+ } else {
86
+ // Otherwise, we need to combine these multiple responses for the same status code into a singular entry
87
+ const description =
88
+ "One of:\n" +
89
+ response . map ( ( r ) => `- ${ r . id } : ${ r . description } ` ) . join ( "\n" ) +
90
+ "\n\n**See examples dropdown below**" ;
91
+ const schemas = response . map ( ( r ) => r . schema ) as [ ZodType , ZodType , ...ZodType [ ] ] ;
92
+ const examples = response . reduce < Record < string , ExampleObject > > ( ( acc , r ) => {
93
+ const example = r . schema . _def . openapi ?. metadata ?. example ;
94
+ if ( example ) {
95
+ if ( acc [ r . id ] ) {
96
+ throw Error (
97
+ `Duplicate definition of response id ${ r . id } for ${ specification . method } ${ specification . path } status ${ statusCode } ` ,
98
+ ) ;
99
+ }
100
+ acc [ r . id ] = {
101
+ description : r . description ,
102
+ value : example ,
103
+ } ;
104
+ }
105
+ return acc ;
106
+ } , { } ) ;
107
+ responses [ statusCode ] = {
108
+ description,
109
+ content : {
110
+ "application/json" : {
111
+ schema : z . union ( schemas ) ,
112
+ examples,
113
+ } ,
114
+ } ,
115
+ } ;
116
+ }
117
+ }
118
+
119
+ return responses ;
120
+ }
121
+
63
122
export function registerPathSpecification <
64
123
Params extends AnyZodObject ,
65
124
Query extends AnyZodObject ,
@@ -83,17 +142,7 @@ export function registerPathSpecification<
83
142
combinedDescription = descriptionHeader || description ;
84
143
}
85
144
86
- const responses : RouteConfig [ "responses" ] = { } ;
87
- for ( const [ statusCode , response ] of Object . entries ( specification . responses ) ) {
88
- responses [ statusCode ] = {
89
- description : response . description ,
90
- content : {
91
- "application/json" : {
92
- schema : response . schema ,
93
- } ,
94
- } ,
95
- } ;
96
- }
145
+ const responses = getPathResponsesForSpecification ( specification ) ;
97
146
98
147
const request : RouteConfig [ "request" ] = { params, query } ;
99
148
if ( specification . body ) {
0 commit comments