diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2af7892..e06fcab 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -123,7 +123,33 @@ describe("Python generator", () => { ); const media = createWidget && requestMedia(createWidget); if (media) { - media[1].example = { description: null, name: "Widget", provider: "cloud", region: "us", settings: {} }; + media[1].schema = { + anyOf: [ + { + additionalProperties: false, + example: { labels: [{ bbox: [0.1, 0.2, 0.3, 0.4], classId: 0 }] }, + properties: { + labels: { + items: { + properties: { + bbox: { + prefixItems: Array.from({ length: 4 }, () => ({ type: "number" })), + type: "array", + }, + classId: { minimum: 0, type: "integer" }, + }, + required: ["classId"], + type: "object", + }, + type: "array", + }, + }, + required: ["labels"], + type: "object", + }, + { properties: { metadata: { type: "object" } }, required: ["metadata"], type: "object" }, + ], + }; } const echo = getOperations(source).find((operation) => operation.path === "/echo"); const echoMedia = echo && requestMedia(echo); @@ -144,7 +170,7 @@ describe("Python generator", () => { expect(sample?.source).toContain("response = client.widgets.retrieve("); expect(sample?.source).toContain('widget_id="widget_123"'); const createSample = decorated.paths["/widgets"]?.post?.["x-codeSamples"]?.[0]; - expect(createSample?.source).toContain('body={"description": None'); + expect(createSample?.source).toContain('body={"labels": [{"bbox": [0.1, 0.2, 0.3, 0.4], "classId": 0}]}'); const echoSample = decorated.paths["/echo"]?.post?.["x-codeSamples"]?.[0]; expect(echoSample?.source).toContain("body=None"); @@ -614,6 +640,12 @@ describe("Python generator", () => { ).toBe("examplexxx"); expect(schemaExample(document, { type: "null" })).toBeNull(); expect(schemaExample(document, { type: ["null"] })).toBeNull(); + expect( + schemaExample(document, { + prefixItems: Array.from({ length: 4 }, () => ({ type: "number" })), + type: "array", + }), + ).toEqual([1, 1, 1, 1]); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index dc32a2e..553f7c7 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -27,6 +27,7 @@ export interface JsonSchema { multipleOf?: number; nullable?: boolean; oneOf?: JsonSchema[]; + prefixItems?: JsonSchema[]; properties?: Record; propertyNames?: JsonSchema; pattern?: string; @@ -536,12 +537,15 @@ export function pythonCodeSample( bodyValue?: unknown, ): string { const request = requestMedia(operation); + const generatedBody = request ? requestBodyExample(document, operation) : ""; const exampleBody = bodyValue !== undefined ? bodyValue : request?.[1].example !== undefined ? request[1].example - : schemaExample(document, request?.[1].schema); + : request?.[0].startsWith("text/") + ? generatedBody + : JSON.parse(generatedBody || "null"); const bodyValues = exampleBody && typeof exampleBody === "object" && !Array.isArray(exampleBody) ? (exampleBody as Record) @@ -1286,7 +1290,18 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch if (Array.isArray(value)) { if (schema.minItems !== undefined && value.length < schema.minItems) return false; if (schema.maxItems !== undefined && value.length > schema.maxItems) return false; - if (schema.items && value.some((item) => !schemaMatches(document, item, schema.items as JsonSchema, depth + 1))) + if ( + schema.prefixItems?.some( + (item, index) => index < value.length && !schemaMatches(document, value[index], item, depth + 1), + ) + ) + return false; + if ( + schema.items && + value + .slice(schema.prefixItems?.length ?? 0) + .some((item) => !schemaMatches(document, item, schema.items as JsonSchema, depth + 1)) + ) return false; } if (value && typeof value === "object" && !Array.isArray(value)) { @@ -1495,8 +1510,17 @@ export function schemaExample( if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.every((value) => value === "null"))) return null; if (type === "array") { - const length = schema.maxItems === 0 ? 0 : Math.max(1, schema.minItems ?? 0); - return Array.from({ length }, () => schemaExample(document, schema.items, depth + 1, name)); + const prefix = (schema.prefixItems ?? []).map((item) => schemaExample(document, item, depth + 1, name)); + const length = Math.min( + schema.maxItems ?? Number.POSITIVE_INFINITY, + Math.max(prefix.length, 1, schema.minItems ?? 0), + ); + return [ + ...prefix.slice(0, length), + ...Array.from({ length: Math.max(0, length - prefix.length) }, () => + schemaExample(document, schema.items, depth + 1, name), + ), + ]; } if (type === "boolean") return false; if (type === "integer" || type === "number") {