@pikku/openapi-parser 0.12.4 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/codegen.js +5 -8
- package/dist/parse-openapi.js +44 -30
- package/package.json +1 -1
- package/src/codegen.ts +5 -7
- package/src/parse-openapi.test.ts +152 -1
- package/src/parse-openapi.ts +39 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @pikku/openapi-parser
|
|
2
2
|
|
|
3
|
+
## 0.12.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a57ff11: Add Swagger 2.0 support: extract requestBody from body parameters, responseSchema from direct response schema, and component schemas from definitions. Fix duplicate .describe() on request body properties.
|
|
8
|
+
|
|
9
|
+
## 0.12.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 8552e18: Don't generate `output: z.void()` for operations without response schemas — omit the field instead
|
|
14
|
+
|
|
3
15
|
## 0.12.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/codegen.js
CHANGED
|
@@ -119,7 +119,10 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags) {
|
|
|
119
119
|
const outputName = `${pascalName}Output`;
|
|
120
120
|
// Determine error imports needed
|
|
121
121
|
const errorClasses = getErrorClassesForResponses(parsed.errorResponses);
|
|
122
|
-
|
|
122
|
+
const needsZod = hasInput || !!parsed.responseSchema;
|
|
123
|
+
if (needsZod) {
|
|
124
|
+
lines.push("import { z } from 'zod'");
|
|
125
|
+
}
|
|
123
126
|
lines.push("import { pikkuSessionlessFunc } from '#pikku'");
|
|
124
127
|
if (errorClasses.length > 0) {
|
|
125
128
|
lines.push(`import { ${errorClasses.join(', ')} } from '@pikku/core/errors'`);
|
|
@@ -148,9 +151,6 @@ function generateFunctionFile(named, parsed, vars, ctx, spec, flags) {
|
|
|
148
151
|
if (parsed.responseSchema) {
|
|
149
152
|
funcConfig.push(` output: ${outputName},`);
|
|
150
153
|
}
|
|
151
|
-
else {
|
|
152
|
-
funcConfig.push(' output: z.void(),');
|
|
153
|
-
}
|
|
154
154
|
if (errorClasses.length > 0) {
|
|
155
155
|
funcConfig.push(` errors: [${errorClasses.join(', ')}],`);
|
|
156
156
|
}
|
|
@@ -186,10 +186,7 @@ function buildInputSchema(parsed, ctx) {
|
|
|
186
186
|
continue;
|
|
187
187
|
const isOptional = !requiredSet.has(key);
|
|
188
188
|
const zodCode = schemaToZod(propSchema, ctx, { optional: isOptional });
|
|
189
|
-
|
|
190
|
-
? `${zodCode}.describe(${JSON.stringify(propSchema.description)})`
|
|
191
|
-
: zodCode;
|
|
192
|
-
props.push(` ${safeKey(key)}: ${withDesc},`);
|
|
189
|
+
props.push(` ${safeKey(key)}: ${zodCode},`);
|
|
193
190
|
}
|
|
194
191
|
}
|
|
195
192
|
else {
|
package/dist/parse-openapi.js
CHANGED
|
@@ -38,12 +38,11 @@ export async function parseOpenAPISpec(filePath) {
|
|
|
38
38
|
const authType = detectAuthType(doc);
|
|
39
39
|
const securitySchemes = extractSecuritySchemes(doc);
|
|
40
40
|
const tagDescriptions = extractTagDescriptions(doc);
|
|
41
|
-
// Extract component schemas
|
|
41
|
+
// Extract component schemas (OpenAPI 3.x: components.schemas, Swagger 2.x: definitions)
|
|
42
42
|
const componentSchemas = {};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
43
|
+
const rawSchemas = doc.components?.schemas ?? doc.definitions ?? {};
|
|
44
|
+
for (const [name, schema] of Object.entries(rawSchemas)) {
|
|
45
|
+
componentSchemas[name] = schema;
|
|
47
46
|
}
|
|
48
47
|
// Extract operations (skip deprecated)
|
|
49
48
|
const operations = [];
|
|
@@ -59,7 +58,9 @@ export async function parseOpenAPISpec(filePath) {
|
|
|
59
58
|
if (op.deprecated === true)
|
|
60
59
|
continue;
|
|
61
60
|
const allParams = [...sharedParams, ...(op.parameters ?? [])];
|
|
62
|
-
|
|
61
|
+
// OpenAPI 3.x: op.requestBody, Swagger 2.x: parameters[].in === 'body'
|
|
62
|
+
const body3 = op.requestBody;
|
|
63
|
+
const body2 = allParams.find((p) => p.in === 'body');
|
|
63
64
|
operations.push({
|
|
64
65
|
operationId: op.operationId,
|
|
65
66
|
method,
|
|
@@ -71,8 +72,8 @@ export async function parseOpenAPISpec(filePath) {
|
|
|
71
72
|
queryParams: extractParams(allParams, 'query'),
|
|
72
73
|
headerParams: extractParams(allParams, 'header'),
|
|
73
74
|
requestBody: extractRequestBody(op),
|
|
74
|
-
requestBodyDescription:
|
|
75
|
-
requestBodyRequired:
|
|
75
|
+
requestBodyDescription: body3?.description ?? body2?.description,
|
|
76
|
+
requestBodyRequired: body3?.required ?? body2?.required,
|
|
76
77
|
responseSchema: extractResponseSchema(op),
|
|
77
78
|
responseDescription: extractResponseDescription(op),
|
|
78
79
|
errorResponses: extractErrorResponses(op),
|
|
@@ -184,20 +185,25 @@ function extractParams(params, location) {
|
|
|
184
185
|
}));
|
|
185
186
|
}
|
|
186
187
|
function extractRequestBody(op) {
|
|
188
|
+
// OpenAPI 3.x: op.requestBody.content['application/json'].schema
|
|
187
189
|
const body = op.requestBody;
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
if (body) {
|
|
191
|
+
const content = body.content;
|
|
192
|
+
if (content) {
|
|
193
|
+
const jsonContent = content['application/json'];
|
|
194
|
+
if (jsonContent?.schema)
|
|
195
|
+
return jsonContent.schema;
|
|
196
|
+
const firstKey = Object.keys(content)[0];
|
|
197
|
+
if (firstKey && content[firstKey]?.schema) {
|
|
198
|
+
return content[firstKey].schema;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Swagger 2.x: parameters[].in === 'body' → schema
|
|
203
|
+
if (op.parameters) {
|
|
204
|
+
const bodyParam = op.parameters.find((p) => p.in === 'body');
|
|
205
|
+
if (bodyParam?.schema)
|
|
206
|
+
return bodyParam.schema;
|
|
201
207
|
}
|
|
202
208
|
return undefined;
|
|
203
209
|
}
|
|
@@ -221,24 +227,32 @@ function extractResponseSchema(op) {
|
|
|
221
227
|
const resp = responses[code];
|
|
222
228
|
if (!resp)
|
|
223
229
|
continue;
|
|
230
|
+
// OpenAPI 3.x: resp.content['application/json'].schema
|
|
224
231
|
const content = resp.content;
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
if (content) {
|
|
233
|
+
const jsonContent = content['application/json'];
|
|
234
|
+
if (jsonContent?.schema)
|
|
235
|
+
return jsonContent.schema;
|
|
236
|
+
const firstKey = Object.keys(content)[0];
|
|
237
|
+
if (firstKey && content[firstKey]?.schema) {
|
|
238
|
+
return content[firstKey].schema;
|
|
239
|
+
}
|
|
233
240
|
}
|
|
241
|
+
// Swagger 2.x: resp.schema directly
|
|
242
|
+
if (resp.schema)
|
|
243
|
+
return resp.schema;
|
|
234
244
|
}
|
|
235
245
|
// Fallback: any 2xx
|
|
236
246
|
for (const [code, resp] of Object.entries(responses)) {
|
|
237
|
-
if (code.startsWith('2')
|
|
247
|
+
if (!code.startsWith('2'))
|
|
248
|
+
continue;
|
|
249
|
+
if (resp.content) {
|
|
238
250
|
const jsonContent = resp.content['application/json'];
|
|
239
251
|
if (jsonContent?.schema)
|
|
240
252
|
return jsonContent.schema;
|
|
241
253
|
}
|
|
254
|
+
if (resp.schema)
|
|
255
|
+
return resp.schema;
|
|
242
256
|
}
|
|
243
257
|
return undefined;
|
|
244
258
|
}
|
package/package.json
CHANGED
package/src/codegen.ts
CHANGED
|
@@ -211,7 +211,10 @@ function generateFunctionFile(
|
|
|
211
211
|
// Determine error imports needed
|
|
212
212
|
const errorClasses = getErrorClassesForResponses(parsed.errorResponses)
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
const needsZod = hasInput || !!parsed.responseSchema
|
|
215
|
+
if (needsZod) {
|
|
216
|
+
lines.push("import { z } from 'zod'")
|
|
217
|
+
}
|
|
215
218
|
lines.push("import { pikkuSessionlessFunc } from '#pikku'")
|
|
216
219
|
|
|
217
220
|
if (errorClasses.length > 0) {
|
|
@@ -246,8 +249,6 @@ function generateFunctionFile(
|
|
|
246
249
|
if (hasInput) funcConfig.push(` input: ${inputName},`)
|
|
247
250
|
if (parsed.responseSchema) {
|
|
248
251
|
funcConfig.push(` output: ${outputName},`)
|
|
249
|
-
} else {
|
|
250
|
-
funcConfig.push(' output: z.void(),')
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
if (errorClasses.length > 0) {
|
|
@@ -303,10 +304,7 @@ function buildInputSchema(
|
|
|
303
304
|
if (propSchema.readOnly) continue
|
|
304
305
|
const isOptional = !requiredSet.has(key)
|
|
305
306
|
const zodCode = schemaToZod(propSchema, ctx, { optional: isOptional })
|
|
306
|
-
|
|
307
|
-
? `${zodCode}.describe(${JSON.stringify(propSchema.description)})`
|
|
308
|
-
: zodCode
|
|
309
|
-
props.push(` ${safeKey(key)}: ${withDesc},`)
|
|
307
|
+
props.push(` ${safeKey(key)}: ${zodCode},`)
|
|
310
308
|
}
|
|
311
309
|
} else {
|
|
312
310
|
const bodyZod = schemaToZod(parsed.requestBody, ctx)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { strict as assert } from 'assert'
|
|
2
2
|
import { describe, test } from 'node:test'
|
|
3
|
-
import {
|
|
3
|
+
import { writeFile, rm, mkdtemp } from 'fs/promises'
|
|
4
|
+
import { join } from 'path'
|
|
5
|
+
import { tmpdir } from 'os'
|
|
6
|
+
import { computeContractHash, parseOpenAPISpec } from './parse-openapi.js'
|
|
4
7
|
import type { ParsedSpec } from './parse-openapi.js'
|
|
5
8
|
|
|
6
9
|
function makeSpec(overrides: Partial<ParsedSpec> = {}): ParsedSpec {
|
|
@@ -148,3 +151,151 @@ describe('computeContractHash', () => {
|
|
|
148
151
|
)
|
|
149
152
|
})
|
|
150
153
|
})
|
|
154
|
+
|
|
155
|
+
describe('parseOpenAPISpec — Swagger 2.0', () => {
|
|
156
|
+
const swagger2Spec = {
|
|
157
|
+
swagger: '2.0',
|
|
158
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
159
|
+
host: 'api.example.com',
|
|
160
|
+
basePath: '/v1',
|
|
161
|
+
schemes: ['https'],
|
|
162
|
+
definitions: {
|
|
163
|
+
Pet: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
required: ['name'],
|
|
166
|
+
properties: {
|
|
167
|
+
id: { type: 'integer', format: 'int64' },
|
|
168
|
+
name: { type: 'string' },
|
|
169
|
+
status: { type: 'string', enum: ['available', 'sold'], description: 'pet status' },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
securityDefinitions: {
|
|
174
|
+
api_key: { type: 'apiKey', name: 'api_key', in: 'header' },
|
|
175
|
+
},
|
|
176
|
+
paths: {
|
|
177
|
+
'/pets': {
|
|
178
|
+
get: {
|
|
179
|
+
operationId: 'listPets',
|
|
180
|
+
summary: 'List all pets',
|
|
181
|
+
parameters: [
|
|
182
|
+
{ in: 'query', name: 'status', type: 'string', description: 'Filter by status' },
|
|
183
|
+
],
|
|
184
|
+
responses: {
|
|
185
|
+
'200': {
|
|
186
|
+
description: 'A list of pets',
|
|
187
|
+
schema: { type: 'array', items: { $ref: '#/definitions/Pet' } },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
post: {
|
|
192
|
+
operationId: 'createPet',
|
|
193
|
+
summary: 'Create a pet',
|
|
194
|
+
parameters: [
|
|
195
|
+
{
|
|
196
|
+
in: 'body',
|
|
197
|
+
name: 'body',
|
|
198
|
+
description: 'Pet to create',
|
|
199
|
+
required: true,
|
|
200
|
+
schema: { $ref: '#/definitions/Pet' },
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
responses: {
|
|
204
|
+
'201': {
|
|
205
|
+
description: 'Created',
|
|
206
|
+
schema: { $ref: '#/definitions/Pet' },
|
|
207
|
+
},
|
|
208
|
+
'400': { description: 'Bad request' },
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
'/pets/{petId}': {
|
|
213
|
+
get: {
|
|
214
|
+
operationId: 'getPet',
|
|
215
|
+
parameters: [
|
|
216
|
+
{ in: 'path', name: 'petId', type: 'integer', required: true },
|
|
217
|
+
],
|
|
218
|
+
responses: {
|
|
219
|
+
'200': {
|
|
220
|
+
description: 'A pet',
|
|
221
|
+
schema: { $ref: '#/definitions/Pet' },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let tmpDir: string
|
|
230
|
+
let specPath: string
|
|
231
|
+
|
|
232
|
+
test('setup', async () => {
|
|
233
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'openapi-test-'))
|
|
234
|
+
specPath = join(tmpDir, 'spec.json')
|
|
235
|
+
await writeFile(specPath, JSON.stringify(swagger2Spec))
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('extracts component schemas from definitions', async () => {
|
|
239
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
240
|
+
assert.ok(spec.componentSchemas.Pet)
|
|
241
|
+
assert.equal(spec.componentSchemas.Pet.type, 'object')
|
|
242
|
+
assert.ok(spec.componentSchemas.Pet.properties?.name)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('extracts server URLs from host/basePath/schemes', async () => {
|
|
246
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
247
|
+
assert.deepEqual(spec.serverUrls, ['https://api.example.com/v1'])
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('detects apiKey auth from securityDefinitions', async () => {
|
|
251
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
252
|
+
assert.equal(spec.authType, 'apiKey')
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('extracts requestBody from body parameter', async () => {
|
|
256
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
257
|
+
const createPet = spec.operations.find(op => op.operationId === 'createPet')
|
|
258
|
+
assert.ok(createPet?.requestBody)
|
|
259
|
+
assert.equal(createPet.requestBody.type, 'object')
|
|
260
|
+
assert.ok(createPet.requestBody.properties?.name)
|
|
261
|
+
assert.equal(createPet.requestBodyRequired, true)
|
|
262
|
+
assert.equal(createPet.requestBodyDescription, 'Pet to create')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test('extracts responseSchema from Swagger 2.0 response', async () => {
|
|
266
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
267
|
+
const createPet = spec.operations.find(op => op.operationId === 'createPet')
|
|
268
|
+
assert.ok(createPet?.responseSchema)
|
|
269
|
+
assert.equal(createPet.responseSchema.type, 'object')
|
|
270
|
+
|
|
271
|
+
const listPets = spec.operations.find(op => op.operationId === 'listPets')
|
|
272
|
+
assert.ok(listPets?.responseSchema)
|
|
273
|
+
assert.equal(listPets.responseSchema.type, 'array')
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test('extracts query params', async () => {
|
|
277
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
278
|
+
const listPets = spec.operations.find(op => op.operationId === 'listPets')
|
|
279
|
+
assert.equal(listPets?.queryParams.length, 1)
|
|
280
|
+
assert.equal(listPets?.queryParams[0].name, 'status')
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test('extracts path params', async () => {
|
|
284
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
285
|
+
const getPet = spec.operations.find(op => op.operationId === 'getPet')
|
|
286
|
+
assert.equal(getPet?.pathParams.length, 1)
|
|
287
|
+
assert.equal(getPet?.pathParams[0].name, 'petId')
|
|
288
|
+
assert.equal(getPet?.pathParams[0].required, true)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('extracts error responses', async () => {
|
|
292
|
+
const spec = await parseOpenAPISpec(specPath)
|
|
293
|
+
const createPet = spec.operations.find(op => op.operationId === 'createPet')
|
|
294
|
+
assert.equal(createPet?.errorResponses.length, 1)
|
|
295
|
+
assert.equal(createPet?.errorResponses[0].statusCode, 400)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('cleanup', async () => {
|
|
299
|
+
await rm(tmpDir, { recursive: true, force: true })
|
|
300
|
+
})
|
|
301
|
+
})
|
package/src/parse-openapi.ts
CHANGED
|
@@ -106,12 +106,11 @@ export async function parseOpenAPISpec(filePath: string): Promise<ParsedSpec> {
|
|
|
106
106
|
const securitySchemes = extractSecuritySchemes(doc)
|
|
107
107
|
const tagDescriptions = extractTagDescriptions(doc)
|
|
108
108
|
|
|
109
|
-
// Extract component schemas
|
|
109
|
+
// Extract component schemas (OpenAPI 3.x: components.schemas, Swagger 2.x: definitions)
|
|
110
110
|
const componentSchemas: Record<string, OpenAPISchema> = {}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
111
|
+
const rawSchemas = doc.components?.schemas ?? doc.definitions ?? {}
|
|
112
|
+
for (const [name, schema] of Object.entries(rawSchemas)) {
|
|
113
|
+
componentSchemas[name] = schema as OpenAPISchema
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
// Extract operations (skip deprecated)
|
|
@@ -132,7 +131,9 @@ export async function parseOpenAPISpec(filePath: string): Promise<ParsedSpec> {
|
|
|
132
131
|
|
|
133
132
|
const allParams: any[] = [...sharedParams, ...(op.parameters ?? [])]
|
|
134
133
|
|
|
135
|
-
|
|
134
|
+
// OpenAPI 3.x: op.requestBody, Swagger 2.x: parameters[].in === 'body'
|
|
135
|
+
const body3 = op.requestBody
|
|
136
|
+
const body2 = allParams.find((p: any) => p.in === 'body')
|
|
136
137
|
|
|
137
138
|
operations.push({
|
|
138
139
|
operationId: op.operationId,
|
|
@@ -145,8 +146,8 @@ export async function parseOpenAPISpec(filePath: string): Promise<ParsedSpec> {
|
|
|
145
146
|
queryParams: extractParams(allParams, 'query'),
|
|
146
147
|
headerParams: extractParams(allParams, 'header'),
|
|
147
148
|
requestBody: extractRequestBody(op),
|
|
148
|
-
requestBodyDescription:
|
|
149
|
-
requestBodyRequired:
|
|
149
|
+
requestBodyDescription: body3?.description ?? body2?.description,
|
|
150
|
+
requestBodyRequired: body3?.required ?? body2?.required,
|
|
150
151
|
responseSchema: extractResponseSchema(op),
|
|
151
152
|
responseDescription: extractResponseDescription(op),
|
|
152
153
|
errorResponses: extractErrorResponses(op),
|
|
@@ -266,20 +267,25 @@ function extractParams(
|
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
function extractRequestBody(op: any): OpenAPISchema | undefined {
|
|
270
|
+
// OpenAPI 3.x: op.requestBody.content['application/json'].schema
|
|
269
271
|
const body = op.requestBody
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
if (body) {
|
|
273
|
+
const content = body.content
|
|
274
|
+
if (content) {
|
|
275
|
+
const jsonContent = content['application/json']
|
|
276
|
+
if (jsonContent?.schema) return jsonContent.schema as OpenAPISchema
|
|
274
277
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
const firstKey = Object.keys(content)[0]
|
|
279
|
+
if (firstKey && content[firstKey]?.schema) {
|
|
280
|
+
return content[firstKey].schema as OpenAPISchema
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
278
284
|
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return
|
|
285
|
+
// Swagger 2.x: parameters[].in === 'body' → schema
|
|
286
|
+
if (op.parameters) {
|
|
287
|
+
const bodyParam = op.parameters.find((p: any) => p.in === 'body')
|
|
288
|
+
if (bodyParam?.schema) return bodyParam.schema as OpenAPISchema
|
|
283
289
|
}
|
|
284
290
|
|
|
285
291
|
return undefined
|
|
@@ -304,24 +310,30 @@ function extractResponseSchema(op: any): OpenAPISchema | undefined {
|
|
|
304
310
|
const resp = responses[code]
|
|
305
311
|
if (!resp) continue
|
|
306
312
|
|
|
313
|
+
// OpenAPI 3.x: resp.content['application/json'].schema
|
|
307
314
|
const content = resp.content
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (jsonContent?.schema) return jsonContent.schema as OpenAPISchema
|
|
315
|
+
if (content) {
|
|
316
|
+
const jsonContent = content['application/json']
|
|
317
|
+
if (jsonContent?.schema) return jsonContent.schema as OpenAPISchema
|
|
312
318
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
const firstKey = Object.keys(content)[0]
|
|
320
|
+
if (firstKey && content[firstKey]?.schema) {
|
|
321
|
+
return content[firstKey].schema as OpenAPISchema
|
|
322
|
+
}
|
|
316
323
|
}
|
|
324
|
+
|
|
325
|
+
// Swagger 2.x: resp.schema directly
|
|
326
|
+
if (resp.schema) return resp.schema as OpenAPISchema
|
|
317
327
|
}
|
|
318
328
|
|
|
319
329
|
// Fallback: any 2xx
|
|
320
330
|
for (const [code, resp] of Object.entries(responses) as [string, any][]) {
|
|
321
|
-
if (code.startsWith('2')
|
|
331
|
+
if (!code.startsWith('2')) continue
|
|
332
|
+
if (resp.content) {
|
|
322
333
|
const jsonContent = resp.content['application/json']
|
|
323
334
|
if (jsonContent?.schema) return jsonContent.schema as OpenAPISchema
|
|
324
335
|
}
|
|
336
|
+
if (resp.schema) return resp.schema as OpenAPISchema
|
|
325
337
|
}
|
|
326
338
|
|
|
327
339
|
return undefined
|