Skip to content

Commit f479cbc

Browse files
feat(sdk): support Swagger V2 model extraction (#4188)
## Proposed change `extractModelsFromOperation` previously only extracted referenced models from OpenAPI V3 specs. This PR adds Swagger V2 support: - `extractRefModel` now handles both `#/components/schemas/` (V3) and `#/definitions/` (V2) `$ref` paths - Parameters with `in: "body"` and a `schema` field are now processed (V2 body parameters) - Response objects with a top-level `schema` field are now processed (V2 responses) - V3 parameter schemas (path/query params referencing models) are also now extracted - The `isV3` guard in `extractDomains` is removed so model extraction runs for all spec versions A new `extractModelsFromSchema` helper consolidates `$ref` and `items.$ref` extraction logic. ## Related issues *- No issue associated -*
2 parents ce0decb + 11a4918 commit f479cbc

3 files changed

Lines changed: 86 additions & 33 deletions

File tree

packages/@ama-sdk/schematics/cli/genai-context/update-sdk-context.helpers.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
OpenAPIV2,
23
OpenAPIV3,
34
} from 'openapi-types';
45
import {
@@ -76,6 +77,11 @@ describe('update-sdk-context.helpers', () => {
7677
expect(extractRefModel('#/components/schemas/Order')).toBe('Order');
7778
});
7879

80+
it('should extract model name from Swagger V2 #/definitions/ $ref', () => {
81+
expect(extractRefModel('#/definitions/Pet')).toBe('Pet');
82+
expect(extractRefModel('#/definitions/Order')).toBe('Order');
83+
});
84+
7985
it('should return null for undefined or invalid ref', () => {
8086
expect(extractRefModel(undefined)).toBeNull();
8187
expect(extractRefModel('')).toBeNull();
@@ -143,6 +149,46 @@ describe('update-sdk-context.helpers', () => {
143149
const models = extractModelsFromOperation(operation);
144150
expect(models).toHaveLength(0);
145151
});
152+
153+
it('should extract models from Swagger V2 body parameter', () => {
154+
const operation = {
155+
parameters: [
156+
{ in: 'body', name: 'body', schema: { $ref: '#/definitions/Pet' } }
157+
],
158+
responses: { 200: { description: 'OK' } }
159+
} as unknown as OpenAPIV2.OperationObject;
160+
161+
const models = extractModelsFromOperation(operation);
162+
expect(models).toContain('Pet');
163+
});
164+
165+
it('should extract models from Swagger V2 response schema', () => {
166+
const operation = {
167+
responses: {
168+
200: {
169+
description: 'OK',
170+
schema: { $ref: '#/definitions/Order' }
171+
}
172+
}
173+
} as unknown as OpenAPIV2.OperationObject;
174+
175+
const models = extractModelsFromOperation(operation);
176+
expect(models).toContain('Order');
177+
});
178+
179+
it('should extract models from Swagger V2 array response schema', () => {
180+
const operation = {
181+
responses: {
182+
200: {
183+
description: 'OK',
184+
schema: { type: 'array', items: { $ref: '#/definitions/Pet' } }
185+
}
186+
}
187+
} as unknown as OpenAPIV2.OperationObject;
188+
189+
const models = extractModelsFromOperation(operation);
190+
expect(models).toContain('Pet');
191+
});
146192
});
147193

148194
describe('inferDomainFromPath', () => {

packages/@ama-sdk/schematics/cli/genai-context/update-sdk-context.helpers.ts

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -146,53 +146,63 @@ export function extractRefModel(ref?: string): string | null {
146146
if (!ref) {
147147
return null;
148148
}
149-
const match = ref.match(/#\/components\/schemas\/(\w+)/);
149+
const match = ref.match(/#\/(?:components\/schemas|definitions)\/(\w+)/);
150150
return match ? match[1] : null;
151151
}
152152

153+
/** Schema types that can be passed to extractModelsFromSchema */
154+
type ExtractableSchema = OpenAPIV2.SchemaObject | OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
155+
156+
/**
157+
* Extracts model name from a schema object (handles direct $ref and array items $ref)
158+
* @param schema
159+
*/
160+
function extractModelsFromSchema(schema: ExtractableSchema | undefined): string[] {
161+
if (!schema) {
162+
return [];
163+
}
164+
const ref = '$ref' in schema
165+
? schema.$ref
166+
: ('items' in schema && schema.items && '$ref' in schema.items
167+
? schema.items.$ref
168+
: undefined);
169+
const model = extractRefModel(ref);
170+
return model ? [model] : [];
171+
}
172+
153173
/**
154-
* Extracts all referenced model names from an OpenAPI operation
174+
* Extracts all referenced model names from an OpenAPI operation (supports both V2 and V3)
155175
* @param operation The OpenAPI operation to extract models from
156176
* @returns Array of model names
157177
*/
158-
export function extractModelsFromOperation(operation: OpenAPIV3.OperationObject | OpenAPIV3_1.OperationObject): string[] {
178+
export function extractModelsFromOperation(operation: OpenAPIV2.OperationObject | OpenAPIV3.OperationObject | OpenAPIV3_1.OperationObject): string[] {
159179
const models: string[] = [];
160180

161-
const requestBody = operation.requestBody as OpenAPIV3.RequestBodyObject | undefined;
181+
const requestBody = (operation as OpenAPIV3.OperationObject).requestBody as OpenAPIV3.RequestBodyObject | undefined;
162182
if (requestBody?.content) {
163183
Object.values(requestBody.content).forEach((mediaType) => {
164-
const { schema } = mediaType;
165-
if (schema && '$ref' in schema) {
166-
const model = extractRefModel(schema.$ref);
167-
if (model) {
168-
models.push(model);
169-
}
184+
models.push(...extractModelsFromSchema(mediaType.schema));
185+
});
186+
}
187+
188+
if (operation.parameters) {
189+
operation.parameters.forEach((param) => {
190+
if (!('$ref' in param) && 'schema' in param) {
191+
models.push(...extractModelsFromSchema(param.schema as ExtractableSchema | undefined));
170192
}
171193
});
172194
}
173195

174196
if (operation.responses) {
175197
Object.values(operation.responses).forEach((responseOrRef) => {
176-
const response = responseOrRef as OpenAPIV3.ResponseObject;
177-
if (response.content) {
178-
Object.values(response.content).forEach((mediaType) => {
179-
const schema = mediaType.schema;
180-
if (schema && '$ref' in schema) {
181-
const model = extractRefModel(schema.$ref);
182-
if (model) {
183-
models.push(model);
184-
}
185-
} else if (schema && 'items' in schema) {
186-
const items = schema.items;
187-
if (items && '$ref' in items) {
188-
const itemModel = extractRefModel(items.$ref);
189-
if (itemModel) {
190-
models.push(itemModel);
191-
}
192-
}
193-
}
198+
if ('content' in responseOrRef) {
199+
Object.values((responseOrRef as OpenAPIV3.ResponseObject).content!).forEach((mediaType) => {
200+
models.push(...extractModelsFromSchema(mediaType.schema));
194201
});
195202
}
203+
if ('schema' in responseOrRef) {
204+
models.push(...extractModelsFromSchema((responseOrRef as OpenAPIV2.ResponseObject).schema));
205+
}
196206
});
197207
}
198208

@@ -217,7 +227,6 @@ export function inferDomainFromPath(apiPath: string): string {
217227
*/
218228
export function extractDomains(spec: OpenAPISpec, customDescriptions?: Record<string, string> | null): Map<string, Domain> {
219229
const domains = new Map<string, Domain>();
220-
const isV3 = 'openapi' in spec;
221230

222231
if (spec.tags) {
223232
spec.tags.forEach((tag) => {
@@ -263,9 +272,7 @@ export function extractDomains(spec: OpenAPISpec, customDescriptions?: Record<st
263272
path: apiPath
264273
});
265274

266-
if (isV3) {
267-
extractModelsFromOperation(operation).forEach((model) => domain.models.add(model));
268-
}
275+
extractModelsFromOperation(operation).forEach((model) => domain.models.add(model));
269276
});
270277
});
271278
}

packages/@o3r/testing/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@
253253
"jest-util": "~30.3.0",
254254
"rimraf": "^6.0.1",
255255
"ts-jest": "~29.4.0",
256-
"uuid": "~13.0.0"
256+
"uuid": "~14.0.0"
257257
},
258258
"schematics": "./collection.json",
259259
"ng-update": {

0 commit comments

Comments
 (0)