From e12595f8db960dfff4a49b6a9ea48897b2cb976b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 02:38:10 +0200 Subject: [PATCH 01/80] Improve generated API examples --- lib/generators/python.test.ts | 23 +++++++++----- lib/openapi.ts | 56 +++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2b56275..c9d51f1 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -149,7 +149,7 @@ describe("Python generator", () => { package: config.python.package, }); expect(withoutPlaceholder.paths["/widgets/{widgetId}"]?.get?.["x-codeSamples"]?.[0]?.source).toContain( - 'widget_id_query="..."', + 'widget_id_query="resource-id"', ); }); @@ -183,7 +183,7 @@ describe("Python generator", () => { body: requestBodyExample(document, raw), origin: "https://docs.example.com", }), - ).toContain("-d '...'"); + ).toContain("-d 'example'"); const multipartDocument = structuredClone(document); const multipartUpload = getOperations(multipartDocument).find( (operation) => requestMedia(operation)?.[0] === "multipart/form-data", @@ -226,7 +226,7 @@ describe("Python generator", () => { type: "object", }; const minimalBody = requestBodyExample(minimalDocument, create); - expect(minimalBody).toBe('{\n "name": "..."\n}'); + expect(minimalBody).toBe('{\n "name": "Example name"\n}'); const minimalCurl = curlCodeSample(minimalDocument, create, { body: minimalBody, environment: "EXAMPLE_API_KEY", @@ -255,7 +255,7 @@ describe("Python generator", () => { ], }; expect(JSON.parse(requestBodyExample(minimalDocument, create))).toEqual({ - images: ["..."], + images: ["example-images"], model: "yolo11n.pt", }); createMedia[1].schema = { allOf: [{ example: "authored", type: "string" }, { minLength: 1 }] }; @@ -289,7 +289,7 @@ describe("Python generator", () => { }, }; const unionBody = requestBodyExample(minimalDocument, unionCreate); - expect(unionBody).toBe('{\n "file": "..."\n}'); + expect(unionBody).toBe('{\n "file": "path/to/file"\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { name: "body", required: true, wholeBody: true }, ]); @@ -321,7 +321,7 @@ describe("Python generator", () => { createMedia[1].schema = { example: null, type: ["object", "null"] }; expect(requestBodyExample(minimalDocument, create)).toBe("null"); createMedia[1].schema = { additionalProperties: { type: "string" }, type: "object" }; - expect(requestBodyExample(minimalDocument, create)).toBe('{\n "key": "..."\n}'); + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "key": "example-key"\n}'); createMedia[1].schema = { properties: { id: { readOnly: true, type: "string" } }, type: "object" }; expect(requestBodyExample(minimalDocument, create)).toBe("{}"); minimalDocument.components = { schemas: { Node: { oneOf: [{ $ref: "#/components/schemas/Node" }] } } }; @@ -374,6 +374,13 @@ describe("Python generator", () => { "values: detect, segment", "values: pose", ]); + expect( + schemaConstraints(document, { + maximum: Number.MAX_SAFE_INTEGER, + minimum: -Number.MAX_SAFE_INTEGER, + type: "integer", + }), + ).toEqual([]); expect( schemaConstraints(document, { allOf: [{ enum: ["obb"], type: "string" }], @@ -384,7 +391,9 @@ describe("Python generator", () => { }); test("uses generic string examples", () => { - expect(schemaExample(document, { type: "string" })).toBe("..."); + expect(schemaExample(document, { type: "string" })).toBe("example"); + expect(schemaExample(document, { format: "email", type: "string" })).toBe("jane@example.com"); + expect(schemaExample(document, { type: "string" }, 0, "project")).toBe("example-project"); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index cd57388..2ec6c34 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -503,12 +503,12 @@ export function pythonCodeSample( .map((argument) => { const value = argument.location !== "body" - ? schemaExample(document, argument.schema) + ? schemaExample(document, argument.schema, 0, argument.name) : argument.wholeBody ? exampleBody : bodyValues[argument.name] !== undefined ? bodyValues[argument.name] - : schemaExample(document, argument.schema); + : schemaExample(document, argument.schema, 0, argument.name); return { source: `${argument.pythonName}=${pythonLiteral(value)}`, value }; }); return [ @@ -699,7 +699,37 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde }; } -export function schemaExample(document: OpenApiDocument, input: JsonSchema | undefined, depth = 0): unknown { +function stringExample(schema: JsonSchema, name?: string): string { + if (schema.format === "date") return "2026-01-01"; + if (schema.format === "date-time") return "2026-01-01T00:00:00Z"; + if (schema.format === "email") return "jane@example.com"; + if (schema.format === "uri" || schema.format === "url") return "https://example.com"; + if (schema.format === "uuid") return "123e4567-e89b-12d3-a456-426614174000"; + if (schema.format === "binary") return "path/to/file"; + const key = name?.toLowerCase() ?? ""; + if (key.includes("apikey") || key.includes("api_key")) return "your-api-key"; + if (key === "owner" || key === "username") return "jane-doe"; + if (key === "project" || key.endsWith("projectslug")) return "example-project"; + if (key === "dataset") return "coco8"; + if (key === "model" || key === "basemodel") return "yolo26n"; + if (key === "deployment") return "example-deployment"; + if (key === "id" || key === "_id" || name?.endsWith("Id") || name?.endsWith("_id")) return "resource-id"; + if (key.includes("url")) return "https://example.com"; + if (key.includes("filename")) return "image.jpg"; + if (key.includes("description")) return "Example description"; + if (key === "name" || key.endsWith("name")) return "Example name"; + if (key.includes("message")) return "Operation completed"; + if (key.includes("hash")) return "a1b2c3d4"; + if (key.includes("color")) return "#4f46e5"; + return name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; +} + +export function schemaExample( + document: OpenApiDocument, + input: JsonSchema | undefined, + depth = 0, + name?: string, +): unknown { if (!input || depth > 8) return null; const schema = resolveSchema(document, input) ?? input; if (schema.example !== undefined) return schema.example; @@ -709,20 +739,20 @@ export function schemaExample(document: OpenApiDocument, input: JsonSchema | und const union = schema.oneOf ?? schema.anyOf; if (union?.length) { - const selected = schemaExample(document, union[0], depth + 1); + const selected = schemaExample(document, union[0], depth + 1, name); if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { - const siblings = schemaExample(document, { ...schema, anyOf: undefined, oneOf: undefined }, depth + 1); + const siblings = schemaExample(document, { ...schema, anyOf: undefined, oneOf: undefined }, depth + 1, name); if (siblings && typeof siblings === "object" && !Array.isArray(siblings)) return { ...siblings, ...selected }; } return selected; } if (schema.allOf?.length) { const object = objectSchema(document, schema); - if (object?.properties) return schemaExample(document, { ...object, allOf: undefined }, depth + 1); + if (object?.properties) return schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); } const type = Array.isArray(schema.type) ? schema.type.find((value) => value !== "null") : schema.type; - if (type === "array") return [schemaExample(document, schema.items, depth + 1)]; + if (type === "array") return [schemaExample(document, schema.items, depth + 1, name)]; if (type === "boolean") return false; if (type === "integer" || type === "number") { const multiple = schema.multipleOf && schema.multipleOf > 0 ? schema.multipleOf : undefined; @@ -748,13 +778,13 @@ export function schemaExample(document: OpenApiDocument, input: JsonSchema | und const example = Object.fromEntries( Object.entries(schema.properties ?? {}).map(([name, property]) => [ name, - schemaExample(document, property, depth + 1), + schemaExample(document, property, depth + 1, name), ]), ); if (Object.keys(example).length || typeof schema.additionalProperties !== "object") return example; - return { key: schemaExample(document, schema.additionalProperties, depth + 1) }; + return { key: schemaExample(document, schema.additionalProperties, depth + 1, "key") }; } - return schema.format === "date-time" ? "2026-01-01T00:00:00Z" : "..."; + return stringExample(schema, name); } function isBinarySchema(document: OpenApiDocument, input: JsonSchema | undefined, depth = 0): boolean { @@ -786,10 +816,10 @@ export function schemaConstraints(document: OpenApiDocument, input: JsonSchema | const constraints: string[] = []; if (schema.enum?.length) constraints.push(`values: ${schema.enum.map(String).join(", ")}`); if (typeof schema.exclusiveMinimum === "number") constraints.push(`greater than ${schema.exclusiveMinimum}`); - else if (schema.minimum !== undefined) + else if (schema.minimum !== undefined && schema.minimum !== -Number.MAX_SAFE_INTEGER) constraints.push(`${schema.exclusiveMinimum ? "greater than" : "minimum"} ${schema.minimum}`); if (typeof schema.exclusiveMaximum === "number") constraints.push(`less than ${schema.exclusiveMaximum}`); - else if (schema.maximum !== undefined) + else if (schema.maximum !== undefined && schema.maximum !== Number.MAX_SAFE_INTEGER) constraints.push(`${schema.exclusiveMaximum ? "less than" : "maximum"} ${schema.maximum}`); if (schema.minLength !== undefined) constraints.push(`minimum length ${schema.minLength}`); if (schema.maxLength !== undefined) constraints.push(`maximum length ${schema.maxLength}`); @@ -1034,7 +1064,7 @@ export function curlCodeSample( ): string { const parameterValueOrExample = (parameter: Parameter) => { const value = values[`${parameter.in}:${parameter.name}`]; - if (value === undefined) return schemaExample(document, parameter.schema); + if (value === undefined) return schemaExample(document, parameter.schema, 0, parameter.name); try { return parameterValue(document, parameter, value); } catch { From 4799ac572fd114170409f2625b7a6596de9dddd1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 02:44:33 +0200 Subject: [PATCH 02/80] Describe union request bodies in Python --- lib/generators/python.test.ts | 9 ++++++++- lib/openapi.ts | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index c9d51f1..309a33b 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -270,11 +270,13 @@ describe("Python generator", () => { schema: { oneOf: [ { + description: "Upload a file", properties: { conf: { type: "number" }, file: { format: "binary", type: "string" } }, required: ["file"], type: "object", }, { + description: "Use a source URL", properties: { conf: { type: "number" }, file: { anyOf: [{ format: "binary", type: "string" }, { type: "null" }] }, @@ -291,7 +293,12 @@ describe("Python generator", () => { const unionBody = requestBodyExample(minimalDocument, unionCreate); expect(unionBody).toBe('{\n "file": "path/to/file"\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ - { name: "body", required: true, wholeBody: true }, + { + description: "Upload a file Or Use a source URL", + name: "body", + required: true, + wholeBody: true, + }, ]); expect( curlCodeSample(minimalDocument, unionCreate, { diff --git a/lib/openapi.ts b/lib/openapi.ts index 2ec6c34..6f91e7c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -431,6 +431,9 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) const structured = json || ["application/x-www-form-urlencoded", "multipart/form-data"].includes(media?.[0] ?? ""); const bodySchema = resolveSchema(document, media?.[1].schema); const union = bodySchema?.oneOf ?? bodySchema?.anyOf; + const variantDescriptions = union + ?.map((variant) => resolveSchema(document, variant)?.description) + .filter((description): description is string => Boolean(description)); const body = structured ? objectSchema(document, bodySchema) : undefined; const variants = union?.map((variant) => objectSchema(document, variant)); const exclusiveBody = @@ -452,7 +455,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) } } else if (media) { parameters.push({ - description: media[1].schema?.description ?? "Request body.", + description: bodySchema?.description ?? variantDescriptions?.join(" Or ") ?? "Request body.", location: "body", name: "body", pythonName: "body", From ac2c8f65432853e9c950f32943c000c4da188b39 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 02:49:52 +0200 Subject: [PATCH 03/80] Keep generated examples within schema constraints --- lib/generators/python.test.ts | 5 ++- lib/openapi.ts | 60 +++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 309a33b..5223d65 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -387,7 +387,7 @@ describe("Python generator", () => { minimum: -Number.MAX_SAFE_INTEGER, type: "integer", }), - ).toEqual([]); + ).toEqual([`minimum ${-Number.MAX_SAFE_INTEGER}`, `maximum ${Number.MAX_SAFE_INTEGER}`]); expect( schemaConstraints(document, { allOf: [{ enum: ["obb"], type: "string" }], @@ -401,6 +401,9 @@ describe("Python generator", () => { expect(schemaExample(document, { type: "string" })).toBe("example"); expect(schemaExample(document, { format: "email", type: "string" })).toBe("jane@example.com"); expect(schemaExample(document, { type: "string" }, 0, "project")).toBe("example-project"); + expect(schemaExample(document, { maxLength: 2, type: "string" }, 0, "iconLetter")).toBe("ex"); + expect(schemaExample(document, { pattern: "^[A-Z]{8}$", type: "string" }, 0, "code")).toBe(""); + expect(schemaExample(document, { pattern: "^[a-z0-9]+$", type: "string" }, 0, "model")).toBe("yolo26n"); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index 6f91e7c..3cf69b8 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -703,28 +703,42 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde } function stringExample(schema: JsonSchema, name?: string): string { - if (schema.format === "date") return "2026-01-01"; - if (schema.format === "date-time") return "2026-01-01T00:00:00Z"; - if (schema.format === "email") return "jane@example.com"; - if (schema.format === "uri" || schema.format === "url") return "https://example.com"; - if (schema.format === "uuid") return "123e4567-e89b-12d3-a456-426614174000"; - if (schema.format === "binary") return "path/to/file"; const key = name?.toLowerCase() ?? ""; - if (key.includes("apikey") || key.includes("api_key")) return "your-api-key"; - if (key === "owner" || key === "username") return "jane-doe"; - if (key === "project" || key.endsWith("projectslug")) return "example-project"; - if (key === "dataset") return "coco8"; - if (key === "model" || key === "basemodel") return "yolo26n"; - if (key === "deployment") return "example-deployment"; - if (key === "id" || key === "_id" || name?.endsWith("Id") || name?.endsWith("_id")) return "resource-id"; - if (key.includes("url")) return "https://example.com"; - if (key.includes("filename")) return "image.jpg"; - if (key.includes("description")) return "Example description"; - if (key === "name" || key.endsWith("name")) return "Example name"; - if (key.includes("message")) return "Operation completed"; - if (key.includes("hash")) return "a1b2c3d4"; - if (key.includes("color")) return "#4f46e5"; - return name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; + let value = name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; + if (key === "sourceurl") value = "https://example.com/dataset.zip"; + else if (key === "region") value = "us-east-1"; + else if (key === "model" || key === "basemodel") value = "yolo26n.pt"; + else if (key === "data") value = "ul://jane-doe/datasets/coco8"; + else if (schema.format === "date") value = "2026-01-01"; + else if (schema.format === "date-time") value = "2026-01-01T00:00:00Z"; + else if (schema.format === "email" || key === "client_email") value = "jane@example.com"; + else if (schema.format === "uri" || schema.format === "url") value = "https://example.com"; + else if (schema.format === "uuid") value = "123e4567-e89b-12d3-a456-426614174000"; + else if (schema.format === "binary") value = "path/to/file"; + else if (key.includes("apikey") || key.includes("api_key")) value = "your-api-key"; + else if (key === "owner" || key === "username") value = "jane-doe"; + else if (key === "project" || key.endsWith("projectslug")) value = "example-project"; + else if (key === "dataset") value = "coco8"; + else if (key === "deployment") value = "example-deployment"; + else if (key === "id" || key === "_id" || name?.endsWith("Id") || name?.endsWith("_id")) value = "resource-id"; + else if (key.includes("url")) value = "https://example.com"; + else if (key.includes("filename")) value = "image.jpg"; + else if (key.includes("description")) value = "Example description"; + else if (key === "name" || key.endsWith("name")) value = "Example name"; + else if (key.includes("message")) value = "Operation completed"; + else if (key.includes("hash")) value = "a1b2c3d4"; + else if (key.includes("color")) value = "#4f46e5"; + if (schema.maxLength !== undefined) value = value.slice(0, schema.maxLength); + if (schema.minLength !== undefined) value = value.padEnd(schema.minLength, "x"); + try { + if (schema.pattern && !new RegExp(schema.pattern).test(value)) { + if (key === "model" && new RegExp(schema.pattern).test("yolo26n")) return "yolo26n"; + return ""; + } + } catch { + return ""; + } + return value; } export function schemaExample( @@ -819,10 +833,10 @@ export function schemaConstraints(document: OpenApiDocument, input: JsonSchema | const constraints: string[] = []; if (schema.enum?.length) constraints.push(`values: ${schema.enum.map(String).join(", ")}`); if (typeof schema.exclusiveMinimum === "number") constraints.push(`greater than ${schema.exclusiveMinimum}`); - else if (schema.minimum !== undefined && schema.minimum !== -Number.MAX_SAFE_INTEGER) + else if (schema.minimum !== undefined) constraints.push(`${schema.exclusiveMinimum ? "greater than" : "minimum"} ${schema.minimum}`); if (typeof schema.exclusiveMaximum === "number") constraints.push(`less than ${schema.exclusiveMaximum}`); - else if (schema.maximum !== undefined && schema.maximum !== Number.MAX_SAFE_INTEGER) + else if (schema.maximum !== undefined) constraints.push(`${schema.exclusiveMaximum ? "less than" : "maximum"} ${schema.maximum}`); if (schema.minLength !== undefined) constraints.push(`minimum length ${schema.minLength}`); if (schema.maxLength !== undefined) constraints.push(`maximum length ${schema.maxLength}`); From fb8a630e521c02beb7a3ad7b6485d0de64f75803 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 02:54:49 +0200 Subject: [PATCH 04/80] Preserve constrained fallback examples --- lib/generators/python.test.ts | 7 +++++++ lib/openapi.ts | 16 +++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 5223d65..a8c3153 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -300,6 +300,10 @@ describe("Python generator", () => { wholeBody: true, }, ]); + const undescribedUnion = structuredClone(unionCreate); + const undescribedVariants = requestMedia(undescribedUnion)?.[1].schema?.oneOf; + for (const variant of undescribedVariants ?? []) delete variant.description; + expect(sdkArguments(minimalDocument, undescribedUnion)).toMatchObject([{ description: "Request body." }]); expect( curlCodeSample(minimalDocument, unionCreate, { body: unionBody, @@ -404,6 +408,9 @@ describe("Python generator", () => { expect(schemaExample(document, { maxLength: 2, type: "string" }, 0, "iconLetter")).toBe("ex"); expect(schemaExample(document, { pattern: "^[A-Z]{8}$", type: "string" }, 0, "code")).toBe(""); expect(schemaExample(document, { pattern: "^[a-z0-9]+$", type: "string" }, 0, "model")).toBe("yolo26n"); + expect(schemaExample(document, { maxLength: 6, pattern: "^yolo26n$|^yolo26$", type: "string" }, 0, "model")).toBe( + "yolo26", + ); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index 3cf69b8..3408171 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -434,6 +434,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) const variantDescriptions = union ?.map((variant) => resolveSchema(document, variant)?.description) .filter((description): description is string => Boolean(description)); + const variantDescription = variantDescriptions?.join(" Or "); const body = structured ? objectSchema(document, bodySchema) : undefined; const variants = union?.map((variant) => objectSchema(document, variant)); const exclusiveBody = @@ -455,7 +456,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) } } else if (media) { parameters.push({ - description: bodySchema?.description ?? variantDescriptions?.join(" Or ") ?? "Request body.", + description: bodySchema?.description || variantDescription || "Request body.", location: "body", name: "body", pythonName: "body", @@ -728,11 +729,16 @@ function stringExample(schema: JsonSchema, name?: string): string { else if (key.includes("message")) value = "Operation completed"; else if (key.includes("hash")) value = "a1b2c3d4"; else if (key.includes("color")) value = "#4f46e5"; - if (schema.maxLength !== undefined) value = value.slice(0, schema.maxLength); - if (schema.minLength !== undefined) value = value.padEnd(schema.minLength, "x"); + const constrainLength = (candidate: string) => + candidate.slice(0, schema.maxLength).padEnd(schema.minLength ?? 0, "x"); + value = constrainLength(value); + const pattern = schema.pattern; try { - if (schema.pattern && !new RegExp(schema.pattern).test(value)) { - if (key === "model" && new RegExp(schema.pattern).test("yolo26n")) return "yolo26n"; + if (pattern && !new RegExp(pattern).test(value)) { + if (key === "model") { + const alternate = ["yolo26n", "yolo26"].map(constrainLength).find((item) => new RegExp(pattern).test(item)); + if (alternate) return alternate; + } return ""; } } catch { From 19b0ad60886c2df82de93795622ab9ac1b6e43f5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 02:59:48 +0200 Subject: [PATCH 05/80] Honor composed scalar constraints --- lib/generators/python.test.ts | 4 ++++ lib/openapi.ts | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a8c3153..7826723 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -411,6 +411,10 @@ describe("Python generator", () => { expect(schemaExample(document, { maxLength: 6, pattern: "^yolo26n$|^yolo26$", type: "string" }, 0, "model")).toBe( "yolo26", ); + expect(schemaExample(document, { minLength: 10, oneOf: [{ type: "string" }] })).toBe("examplexxx"); + expect(schemaExample(document, { allOf: [{ type: "string" }, { minLength: 10 }] })).toBe("examplexxx"); + expect(schemaExample(document, { type: "null" })).toBeNull(); + expect(schemaExample(document, { type: ["null"] })).toBeNull(); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index 3408171..764a7d6 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -747,6 +747,20 @@ function stringExample(schema: JsonSchema, name?: string): string { return value; } +function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[]): JsonSchema { + const schemas = inputs.map((schema) => resolveSchema(document, schema) ?? schema); + const result = Object.assign({}, ...schemas); + delete result.$ref; + delete result.allOf; + delete result.anyOf; + delete result.oneOf; + const minimumLengths = schemas.flatMap((schema) => (schema.minLength === undefined ? [] : [schema.minLength])); + const maximumLengths = schemas.flatMap((schema) => (schema.maxLength === undefined ? [] : [schema.maxLength])); + if (minimumLengths.length) result.minLength = Math.max(...minimumLengths); + if (maximumLengths.length) result.maxLength = Math.min(...maximumLengths); + return result; +} + export function schemaExample( document: OpenApiDocument, input: JsonSchema | undefined, @@ -762,6 +776,13 @@ export function schemaExample( const union = schema.oneOf ?? schema.anyOf; if (union?.length) { + const selectedSchema = resolveSchema(document, union[0]) ?? union[0]; + const selectedType = Array.isArray(selectedSchema.type) + ? selectedSchema.type.find((value) => value !== "null") + : selectedSchema.type; + if (selectedType !== "object" && !selectedSchema.properties) { + return schemaExample(document, mergeScalarSchemas(document, [selectedSchema, schema]), depth + 1, name); + } const selected = schemaExample(document, union[0], depth + 1, name); if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { const siblings = schemaExample(document, { ...schema, anyOf: undefined, oneOf: undefined }, depth + 1, name); @@ -772,9 +793,12 @@ export function schemaExample( if (schema.allOf?.length) { const object = objectSchema(document, schema); if (object?.properties) return schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); + return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf]), depth + 1, name); } const type = Array.isArray(schema.type) ? schema.type.find((value) => value !== "null") : schema.type; + if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.every((value) => value === "null"))) + return null; if (type === "array") return [schemaExample(document, schema.items, depth + 1, name)]; if (type === "boolean") return false; if (type === "integer" || type === "number") { From 3fed9fb2dcbd1b3b725cf7a9d52d81721a0d0cd8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:06:12 +0200 Subject: [PATCH 06/80] Flatten composed example constraints --- lib/generators/python.test.ts | 9 +++++ lib/openapi.ts | 64 +++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 7826723..a1ea40e 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -404,6 +404,10 @@ describe("Python generator", () => { test("uses generic string examples", () => { expect(schemaExample(document, { type: "string" })).toBe("example"); expect(schemaExample(document, { format: "email", type: "string" })).toBe("jane@example.com"); + expect(schemaExample(document, { format: "date", type: "string" }, 0, "data")).toBe("2026-01-01"); + expect(schemaExample(document, { format: "uuid", type: "string" }, 0, "model")).toBe( + "123e4567-e89b-12d3-a456-426614174000", + ); expect(schemaExample(document, { type: "string" }, 0, "project")).toBe("example-project"); expect(schemaExample(document, { maxLength: 2, type: "string" }, 0, "iconLetter")).toBe("ex"); expect(schemaExample(document, { pattern: "^[A-Z]{8}$", type: "string" }, 0, "code")).toBe(""); @@ -413,6 +417,11 @@ describe("Python generator", () => { ); expect(schemaExample(document, { minLength: 10, oneOf: [{ type: "string" }] })).toBe("examplexxx"); expect(schemaExample(document, { allOf: [{ type: "string" }, { minLength: 10 }] })).toBe("examplexxx"); + expect( + schemaExample(document, { + allOf: [{ allOf: [{ type: "string" }, { minLength: 10 }, { pattern: "^example" }] }, { pattern: "xxx$" }], + }), + ).toBe("examplexxx"); expect(schemaExample(document, { type: "null" })).toBeNull(); expect(schemaExample(document, { type: ["null"] })).toBeNull(); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); diff --git a/lib/openapi.ts b/lib/openapi.ts index 764a7d6..2cb3784 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -703,19 +703,20 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde }; } -function stringExample(schema: JsonSchema, name?: string): string { +function stringExample(schema: JsonSchema, name?: string, patterns = schema.pattern ? [schema.pattern] : []): string { const key = name?.toLowerCase() ?? ""; let value = name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; - if (key === "sourceurl") value = "https://example.com/dataset.zip"; - else if (key === "region") value = "us-east-1"; - else if (key === "model" || key === "basemodel") value = "yolo26n.pt"; - else if (key === "data") value = "ul://jane-doe/datasets/coco8"; - else if (schema.format === "date") value = "2026-01-01"; + if (schema.format === "date") value = "2026-01-01"; else if (schema.format === "date-time") value = "2026-01-01T00:00:00Z"; - else if (schema.format === "email" || key === "client_email") value = "jane@example.com"; + else if (schema.format === "email") value = "jane@example.com"; else if (schema.format === "uri" || schema.format === "url") value = "https://example.com"; else if (schema.format === "uuid") value = "123e4567-e89b-12d3-a456-426614174000"; else if (schema.format === "binary") value = "path/to/file"; + else if (key === "sourceurl") value = "https://example.com/dataset.zip"; + else if (key === "region") value = "us-east-1"; + else if (key === "model" || key === "basemodel") value = "yolo26n.pt"; + else if (key === "data") value = "ul://jane-doe/datasets/coco8"; + else if (key === "client_email") value = "jane@example.com"; else if (key.includes("apikey") || key.includes("api_key")) value = "your-api-key"; else if (key === "owner" || key === "username") value = "jane-doe"; else if (key === "project" || key.endsWith("projectslug")) value = "example-project"; @@ -731,24 +732,34 @@ function stringExample(schema: JsonSchema, name?: string): string { else if (key.includes("color")) value = "#4f46e5"; const constrainLength = (candidate: string) => candidate.slice(0, schema.maxLength).padEnd(schema.minLength ?? 0, "x"); - value = constrainLength(value); - const pattern = schema.pattern; try { - if (pattern && !new RegExp(pattern).test(value)) { - if (key === "model") { - const alternate = ["yolo26n", "yolo26"].map(constrainLength).find((item) => new RegExp(pattern).test(item)); - if (alternate) return alternate; - } - return ""; - } + const expressions = patterns.map((pattern) => new RegExp(pattern)); + const candidates = [ + value, + ...(key === "model" ? ["yolo26n", "yolo26"] : []), + ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), + ...(key === "region" ? ["us-east-1"] : []), + ].map(constrainLength); + return ( + candidates.find((candidate) => expressions.every((expression) => expression.test(candidate))) ?? "" + ); } catch { return ""; } - return value; } -function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[]): JsonSchema { - const schemas = inputs.map((schema) => resolveSchema(document, schema) ?? schema); +function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], name?: string): JsonSchema { + const flatten = (input: JsonSchema, depth = 0): JsonSchema[] => { + const schema = resolveSchema(document, input) ?? input; + if (depth > 8) return [schema]; + const union = schema.oneOf ?? schema.anyOf; + return [ + schema, + ...(schema.allOf ?? []).flatMap((item) => flatten(item, depth + 1)), + ...(union?.length ? flatten(union[0], depth + 1) : []), + ]; + }; + const schemas = inputs.flatMap((schema) => flatten(schema)); const result = Object.assign({}, ...schemas); delete result.$ref; delete result.allOf; @@ -758,6 +769,17 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[]): Js const maximumLengths = schemas.flatMap((schema) => (schema.maxLength === undefined ? [] : [schema.maxLength])); if (minimumLengths.length) result.minLength = Math.max(...minimumLengths); if (maximumLengths.length) result.maxLength = Math.min(...maximumLengths); + const patterns = [...new Set(schemas.flatMap((schema) => (schema.pattern ? [schema.pattern] : [])))]; + const type = Array.isArray(result.type) ? result.type.find((value: string) => value !== "null") : result.type; + if ( + type === "string" && + result.example === undefined && + result.default === undefined && + result.const === undefined && + !result.enum?.length + ) { + result.example = stringExample(result, name, patterns); + } return result; } @@ -781,7 +803,7 @@ export function schemaExample( ? selectedSchema.type.find((value) => value !== "null") : selectedSchema.type; if (selectedType !== "object" && !selectedSchema.properties) { - return schemaExample(document, mergeScalarSchemas(document, [selectedSchema, schema]), depth + 1, name); + return schemaExample(document, mergeScalarSchemas(document, [selectedSchema, schema], name), depth + 1, name); } const selected = schemaExample(document, union[0], depth + 1, name); if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { @@ -793,7 +815,7 @@ export function schemaExample( if (schema.allOf?.length) { const object = objectSchema(document, schema); if (object?.properties) return schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); - return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf]), depth + 1, name); + return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf], name), depth + 1, name); } const type = Array.isArray(schema.type) ? schema.type.find((value) => value !== "null") : schema.type; From 719e4026040f42a6a5630f9ac17209d52e2ef5a1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:10:22 +0200 Subject: [PATCH 07/80] Intersect composed scalar bounds --- lib/generators/python.test.ts | 17 +++++++++++++++++ lib/openapi.ts | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a1ea40e..94928db 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -412,6 +412,7 @@ describe("Python generator", () => { expect(schemaExample(document, { maxLength: 2, type: "string" }, 0, "iconLetter")).toBe("ex"); expect(schemaExample(document, { pattern: "^[A-Z]{8}$", type: "string" }, 0, "code")).toBe(""); expect(schemaExample(document, { pattern: "^[a-z0-9]+$", type: "string" }, 0, "model")).toBe("yolo26n"); + expect(schemaExample(document, { pattern: "^[a-z]+$", type: "string" }, 0, "model")).toBe("example"); expect(schemaExample(document, { maxLength: 6, pattern: "^yolo26n$|^yolo26$", type: "string" }, 0, "model")).toBe( "yolo26", ); @@ -424,6 +425,22 @@ describe("Python generator", () => { ).toBe("examplexxx"); expect(schemaExample(document, { type: "null" })).toBeNull(); expect(schemaExample(document, { type: ["null"] })).toBeNull(); + expect( + schemaExample(document, { + allOf: [ + { minimum: 20, type: "integer" }, + { minimum: 10, type: "integer" }, + ], + }), + ).toBe(20); + expect( + schemaExample(document, { + allOf: [ + { maximum: 20, type: "integer" }, + { exclusiveMaximum: 10, type: "integer" }, + ], + }), + ).toBe(1); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index 2cb3784..6eef030 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -739,6 +739,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(key === "model" ? ["yolo26n", "yolo26"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), + "example", ].map(constrainLength); return ( candidates.find((candidate) => expressions.every((expression) => expression.test(candidate))) ?? "" @@ -769,6 +770,28 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam const maximumLengths = schemas.flatMap((schema) => (schema.maxLength === undefined ? [] : [schema.maxLength])); if (minimumLengths.length) result.minLength = Math.max(...minimumLengths); if (maximumLengths.length) result.maxLength = Math.min(...maximumLengths); + const minimums = schemas.flatMap((schema) => + typeof schema.exclusiveMinimum === "number" + ? [{ exclusive: true, value: schema.exclusiveMinimum }] + : schema.minimum === undefined + ? [] + : [{ exclusive: schema.exclusiveMinimum === true, value: schema.minimum }], + ); + const maximums = schemas.flatMap((schema) => + typeof schema.exclusiveMaximum === "number" + ? [{ exclusive: true, value: schema.exclusiveMaximum }] + : schema.maximum === undefined + ? [] + : [{ exclusive: schema.exclusiveMaximum === true, value: schema.maximum }], + ); + const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; + const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; + delete result.minimum; + delete result.maximum; + delete result.exclusiveMinimum; + delete result.exclusiveMaximum; + if (minimum) result[minimum.exclusive ? "exclusiveMinimum" : "minimum"] = minimum.value; + if (maximum) result[maximum.exclusive ? "exclusiveMaximum" : "maximum"] = maximum.value; const patterns = [...new Set(schemas.flatMap((schema) => (schema.pattern ? [schema.pattern] : [])))]; const type = Array.isArray(result.type) ? result.type.find((value: string) => value !== "null") : result.type; if ( From 8fc4786277a7abf65b6dda8976756e61887aeb2c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:14:15 +0200 Subject: [PATCH 08/80] Validate composed format examples --- lib/generators/python.test.ts | 17 +++++++++++++++++ lib/openapi.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 94928db..1b6cfda 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -404,6 +404,7 @@ describe("Python generator", () => { test("uses generic string examples", () => { expect(schemaExample(document, { type: "string" })).toBe("example"); expect(schemaExample(document, { format: "email", type: "string" })).toBe("jane@example.com"); + expect(schemaExample(document, { format: "email", maxLength: 10, type: "string" })).toBe("a@b.co"); expect(schemaExample(document, { format: "date", type: "string" }, 0, "data")).toBe("2026-01-01"); expect(schemaExample(document, { format: "uuid", type: "string" }, 0, "model")).toBe( "123e4567-e89b-12d3-a456-426614174000", @@ -441,6 +442,22 @@ describe("Python generator", () => { ], }), ).toBe(1); + expect( + schemaExample(document, { + allOf: [ + { enum: ["a", "b"], type: "string" }, + { enum: ["c", "b"], type: "string" }, + ], + }), + ).toBe("b"); + expect( + schemaExample(document, { + allOf: [ + { multipleOf: 0.2, type: "number" }, + { multipleOf: 0.3, type: "number" }, + ], + }), + ).toBe(1.2); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index 6eef030..a4c9f52 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -736,13 +736,26 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const expressions = patterns.map((pattern) => new RegExp(pattern)); const candidates = [ value, + ...(schema.format === "email" ? ["a@b.co"] : []), + ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), ...(key === "model" ? ["yolo26n", "yolo26"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), "example", ].map(constrainLength); + const formatValid = (candidate: string) => { + if (schema.format === "date") return /^\d{4}-\d{2}-\d{2}$/.test(candidate); + if (schema.format === "date-time") return !Number.isNaN(Date.parse(candidate)); + if (schema.format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(candidate); + if (schema.format === "uri" || schema.format === "url") return URL.canParse(candidate); + if (schema.format === "uuid") + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(candidate); + return true; + }; return ( - candidates.find((candidate) => expressions.every((expression) => expression.test(candidate))) ?? "" + candidates.find( + (candidate) => formatValid(candidate) && expressions.every((expression) => expression.test(candidate)), + ) ?? "" ); } catch { return ""; @@ -793,6 +806,20 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam if (minimum) result[minimum.exclusive ? "exclusiveMinimum" : "minimum"] = minimum.value; if (maximum) result[maximum.exclusive ? "exclusiveMaximum" : "maximum"] = maximum.value; const patterns = [...new Set(schemas.flatMap((schema) => (schema.pattern ? [schema.pattern] : [])))]; + const enums = schemas.filter((schema) => schema.enum).map((schema) => schema.enum ?? []); + if (enums.length) { + result.enum = enums.reduce((values, items) => values.filter((value) => items.includes(value))); + } + const multiples = schemas.flatMap((schema) => (schema.multipleOf === undefined ? [] : [schema.multipleOf])); + if (multiples.length) { + const decimals = Math.max(...multiples.map((value) => (String(value).split(".")[1] ?? "").length)); + const scale = 10 ** decimals; + const gcd = (a: number, b: number): number => (b ? gcd(b, a % b) : a); + result.multipleOf = + multiples + .map((value) => Math.round(value * scale)) + .reduce((multiple, value) => (multiple * value) / gcd(multiple, value)) / scale; + } const type = Array.isArray(result.type) ? result.type.find((value: string) => value !== "null") : result.type; if ( type === "string" && From cee51396a3e205fd4188aa748d5991612aa5ee14 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:27:48 +0200 Subject: [PATCH 09/80] Validate composed scalar examples --- lib/generators/python.test.ts | 30 +++++++++++ lib/openapi.ts | 95 +++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 1b6cfda..6a856f9 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -458,6 +458,36 @@ describe("Python generator", () => { ], }), ).toBe(1.2); + expect( + schemaExample(document, { + allOf: [{ type: "integer" }, { maximum: 2.5, minimum: 1.5, type: "number" }], + }), + ).toBe(2); + expect( + schemaExample(document, { + allOf: [ + { enum: [1, 2], type: "integer" }, + { multipleOf: 2, type: "integer" }, + ], + }), + ).toBe(2); + expect( + schemaExample(document, { + anyOf: [ + { pattern: "^a$", type: "string" }, + { pattern: "^b$", type: "string" }, + ], + pattern: "^b$", + }), + ).toBe("b"); + expect( + schemaExample(document, { + allOf: [ + { multipleOf: 1e-7, type: "number" }, + { multipleOf: 0.3, type: "number" }, + ], + }), + ).toBe(1.2); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect( schemaExample(document, { diff --git a/lib/openapi.ts b/lib/openapi.ts index a4c9f52..d48ebae 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -703,6 +703,16 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde }; } +function stringFormatMatches(value: string, format?: string): boolean { + if (format === "date") return /^\d{4}-\d{2}-\d{2}$/.test(value); + if (format === "date-time") return !Number.isNaN(Date.parse(value)); + if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); + if (format === "uri" || format === "url") return URL.canParse(value); + if (format === "uuid") + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); + return true; +} + function stringExample(schema: JsonSchema, name?: string, patterns = schema.pattern ? [schema.pattern] : []): string { const key = name?.toLowerCase() ?? ""; let value = name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; @@ -731,11 +741,18 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt else if (key.includes("hash")) value = "a1b2c3d4"; else if (key.includes("color")) value = "#4f46e5"; const constrainLength = (candidate: string) => - candidate.slice(0, schema.maxLength).padEnd(schema.minLength ?? 0, "x"); + (schema.maxLength === undefined ? candidate : candidate.slice(0, schema.maxLength)).padEnd( + schema.minLength ?? 0, + "x", + ); try { const expressions = patterns.map((pattern) => new RegExp(pattern)); const candidates = [ value, + ...patterns.flatMap((pattern) => { + const match = pattern.match(/^\^([a-zA-Z0-9._-]+)\$$/); + return match?.[1] ? [match[1]] : []; + }), ...(schema.format === "email" ? ["a@b.co"] : []), ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), ...(key === "model" ? ["yolo26n", "yolo26"] : []), @@ -743,18 +760,11 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(key === "region" ? ["us-east-1"] : []), "example", ].map(constrainLength); - const formatValid = (candidate: string) => { - if (schema.format === "date") return /^\d{4}-\d{2}-\d{2}$/.test(candidate); - if (schema.format === "date-time") return !Number.isNaN(Date.parse(candidate)); - if (schema.format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(candidate); - if (schema.format === "uri" || schema.format === "url") return URL.canParse(candidate); - if (schema.format === "uuid") - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(candidate); - return true; - }; return ( candidates.find( - (candidate) => formatValid(candidate) && expressions.every((expression) => expression.test(candidate)), + (candidate) => + stringFormatMatches(candidate, schema.format) && + expressions.every((expression) => expression.test(candidate)), ) ?? "" ); } catch { @@ -762,6 +772,38 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt } } +function scalarMatches(value: unknown, schema: JsonSchema): boolean { + const type = Array.isArray(schema.type) ? schema.type.filter((item) => item !== "null") : [schema.type]; + if (type.includes("string")) { + if (typeof value !== "string") return false; + if (!stringFormatMatches(value, schema.format)) return false; + if (schema.minLength !== undefined && value.length < schema.minLength) return false; + if (schema.maxLength !== undefined && value.length > schema.maxLength) return false; + if (schema.pattern) { + try { + if (!new RegExp(schema.pattern).test(value)) return false; + } catch { + return false; + } + } + } + if ((type.includes("number") || type.includes("integer")) && typeof value !== "number") return false; + if (type.includes("integer") && !Number.isInteger(value)) return false; + if (typeof value === "number") { + const minimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum; + const maximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum; + const minimumExclusive = typeof schema.exclusiveMinimum === "number" || schema.exclusiveMinimum === true; + const maximumExclusive = typeof schema.exclusiveMaximum === "number" || schema.exclusiveMaximum === true; + if (minimum !== undefined && (value < minimum || (minimumExclusive && value <= minimum))) return false; + if (maximum !== undefined && (value > maximum || (maximumExclusive && value >= maximum))) return false; + if (schema.multipleOf && Math.abs(value / schema.multipleOf - Math.round(value / schema.multipleOf)) > 1e-9) + return false; + } else if (schema.multipleOf !== undefined || schema.minimum !== undefined || schema.maximum !== undefined) { + return false; + } + return true; +} + function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], name?: string): JsonSchema { const flatten = (input: JsonSchema, depth = 0): JsonSchema[] => { const schema = resolveSchema(document, input) ?? input; @@ -779,6 +821,17 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam delete result.allOf; delete result.anyOf; delete result.oneOf; + const types = schemas.flatMap((schema) => { + const values = Array.isArray(schema.type) ? schema.type : [schema.type]; + return values.filter((value): value is string => Boolean(value) && value !== "null"); + }); + if (types.length) { + const unique = new Set(types); + result.type = + unique.has("integer") && [...unique].every((value) => value === "integer" || value === "number") + ? "integer" + : types[0]; + } const minimumLengths = schemas.flatMap((schema) => (schema.minLength === undefined ? [] : [schema.minLength])); const maximumLengths = schemas.flatMap((schema) => (schema.maxLength === undefined ? [] : [schema.maxLength])); if (minimumLengths.length) result.minLength = Math.max(...minimumLengths); @@ -808,11 +861,18 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam const patterns = [...new Set(schemas.flatMap((schema) => (schema.pattern ? [schema.pattern] : [])))]; const enums = schemas.filter((schema) => schema.enum).map((schema) => schema.enum ?? []); if (enums.length) { - result.enum = enums.reduce((values, items) => values.filter((value) => items.includes(value))); + result.enum = enums + .reduce((values, items) => values.filter((value) => items.includes(value))) + .filter((value) => schemas.every((schema) => scalarMatches(value, schema))); } const multiples = schemas.flatMap((schema) => (schema.multipleOf === undefined ? [] : [schema.multipleOf])); if (multiples.length) { - const decimals = Math.max(...multiples.map((value) => (String(value).split(".")[1] ?? "").length)); + const decimals = Math.max( + ...multiples.map((value) => { + const [coefficient, exponent = "0"] = String(value).toLowerCase().split("e"); + return Math.max(0, (coefficient.split(".")[1] ?? "").length - Number(exponent)); + }), + ); const scale = 10 ** decimals; const gcd = (a: number, b: number): number => (b ? gcd(b, a % b) : a); result.multipleOf = @@ -849,11 +909,18 @@ export function schemaExample( const union = schema.oneOf ?? schema.anyOf; if (union?.length) { const selectedSchema = resolveSchema(document, union[0]) ?? union[0]; + const siblings = { ...schema, anyOf: undefined, oneOf: undefined }; const selectedType = Array.isArray(selectedSchema.type) ? selectedSchema.type.find((value) => value !== "null") : selectedSchema.type; if (selectedType !== "object" && !selectedSchema.properties) { - return schemaExample(document, mergeScalarSchemas(document, [selectedSchema, schema], name), depth + 1, name); + const scalar = union + .map((item) => resolveSchema(document, item) ?? item) + .filter((item) => item.type !== "object" && !item.properties) + .map((item) => mergeScalarSchemas(document, [item, siblings], name)) + .find((item) => scalarMatches(schemaExample(document, item, depth + 1, name), item)); + if (scalar) return schemaExample(document, scalar, depth + 1, name); + return schemaExample(document, mergeScalarSchemas(document, [selectedSchema, siblings], name), depth + 1, name); } const selected = schemaExample(document, union[0], depth + 1, name); if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { From ef5461aacdbffe97439490d26df29a20d80354aa Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:35:57 +0200 Subject: [PATCH 10/80] Try every composed example variant --- lib/generators/python.test.ts | 11 +++++++ lib/openapi.ts | 58 +++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 6a856f9..04a207b 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -405,6 +405,8 @@ describe("Python generator", () => { expect(schemaExample(document, { type: "string" })).toBe("example"); expect(schemaExample(document, { format: "email", type: "string" })).toBe("jane@example.com"); expect(schemaExample(document, { format: "email", maxLength: 10, type: "string" })).toBe("a@b.co"); + expect(schemaExample(document, { format: "ipv4", type: "string" })).toBe("192.0.2.1"); + expect(schemaExample(document, { format: "custom", type: "string" })).toBe(""); expect(schemaExample(document, { format: "date", type: "string" }, 0, "data")).toBe("2026-01-01"); expect(schemaExample(document, { format: "uuid", type: "string" }, 0, "model")).toBe( "123e4567-e89b-12d3-a456-426614174000", @@ -450,6 +452,15 @@ describe("Python generator", () => { ], }), ).toBe("b"); + expect( + schemaExample(document, { + anyOf: [ + { pattern: "^a$", type: "string" }, + { properties: { id: { type: "integer" } }, type: "object" }, + ], + pattern: "^b$", + }), + ).toEqual({ id: 1 }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index d48ebae..34bd3f0 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -708,9 +708,20 @@ function stringFormatMatches(value: string, format?: string): boolean { if (format === "date-time") return !Number.isNaN(Date.parse(value)); if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); if (format === "uri" || format === "url") return URL.canParse(value); + if (format === "hostname") + return /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test( + value, + ); + if (format === "ipv4") + return ( + value.split(".").length === 4 && value.split(".").every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255) + ); + if (format === "ipv6") return value.includes(":") && /^[0-9a-f:]+$/i.test(value); + if (format === "time") return /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value); + if (format === "duration") return /^P(?=\d|T\d)/.test(value); if (format === "uuid") return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); - return true; + return !format || format === "binary" || format === "byte" || format === "password"; } function stringExample(schema: JsonSchema, name?: string, patterns = schema.pattern ? [schema.pattern] : []): string { @@ -720,8 +731,15 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt else if (schema.format === "date-time") value = "2026-01-01T00:00:00Z"; else if (schema.format === "email") value = "jane@example.com"; else if (schema.format === "uri" || schema.format === "url") value = "https://example.com"; + else if (schema.format === "hostname") value = "example.com"; + else if (schema.format === "ipv4") value = "192.0.2.1"; + else if (schema.format === "ipv6") value = "2001:db8::1"; + else if (schema.format === "time") value = "12:00:00Z"; + else if (schema.format === "duration") value = "P1D"; else if (schema.format === "uuid") value = "123e4567-e89b-12d3-a456-426614174000"; else if (schema.format === "binary") value = "path/to/file"; + else if (schema.format === "byte") value = "ZXhhbXBsZQ=="; + else if (schema.format === "password") value = "example-password"; else if (key === "sourceurl") value = "https://example.com/dataset.zip"; else if (key === "region") value = "us-east-1"; else if (key === "model" || key === "basemodel") value = "yolo26n.pt"; @@ -765,7 +783,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt (candidate) => stringFormatMatches(candidate, schema.format) && expressions.every((expression) => expression.test(candidate)), - ) ?? "" + ) ?? `<${schema.format ?? "pattern"} value>` ); } catch { return ""; @@ -908,26 +926,26 @@ export function schemaExample( const union = schema.oneOf ?? schema.anyOf; if (union?.length) { - const selectedSchema = resolveSchema(document, union[0]) ?? union[0]; const siblings = { ...schema, anyOf: undefined, oneOf: undefined }; - const selectedType = Array.isArray(selectedSchema.type) - ? selectedSchema.type.find((value) => value !== "null") - : selectedSchema.type; - if (selectedType !== "object" && !selectedSchema.properties) { - const scalar = union - .map((item) => resolveSchema(document, item) ?? item) - .filter((item) => item.type !== "object" && !item.properties) - .map((item) => mergeScalarSchemas(document, [item, siblings], name)) - .find((item) => scalarMatches(schemaExample(document, item, depth + 1, name), item)); - if (scalar) return schemaExample(document, scalar, depth + 1, name); - return schemaExample(document, mergeScalarSchemas(document, [selectedSchema, siblings], name), depth + 1, name); - } - const selected = schemaExample(document, union[0], depth + 1, name); - if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { - const siblings = schemaExample(document, { ...schema, anyOf: undefined, oneOf: undefined }, depth + 1, name); - if (siblings && typeof siblings === "object" && !Array.isArray(siblings)) return { ...siblings, ...selected }; + for (const item of union) { + const variant = resolveSchema(document, item) ?? item; + const type = Array.isArray(variant.type) ? variant.type.find((value) => value !== "null") : variant.type; + if (variant.type === "null" || (Array.isArray(variant.type) && variant.type.every((value) => value === "null"))) + return null; + if (type === "object" || variant.properties) { + const selected = schemaExample(document, variant, depth + 1, name); + if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { + const enclosing = schemaExample(document, siblings, depth + 1, name); + if (enclosing && typeof enclosing === "object" && !Array.isArray(enclosing)) + return { ...enclosing, ...selected }; + } + return selected; + } + const merged = mergeScalarSchemas(document, [variant, siblings], name); + const selected = schemaExample(document, merged, depth + 1, name); + if (scalarMatches(selected, merged)) return selected; } - return selected; + return schemaExample(document, union[0], depth + 1, name); } if (schema.allOf?.length) { const object = objectSchema(document, schema); From ba7a7a6c82fb7fc18ec9f7f5e6f7a524a1ed24d8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:41:56 +0200 Subject: [PATCH 11/80] Validate composed examples against full schemas --- lib/generators/python.test.ts | 36 +++++++++++++ lib/openapi.ts | 98 ++++++++++++++++++++++++++++------- 2 files changed, 116 insertions(+), 18 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 04a207b..ca00b07 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -408,6 +408,7 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "ipv4", type: "string" })).toBe("192.0.2.1"); expect(schemaExample(document, { format: "custom", type: "string" })).toBe(""); expect(schemaExample(document, { format: "date", type: "string" }, 0, "data")).toBe("2026-01-01"); + expect(schemaExample(document, { format: "date-time", maxLength: 10, type: "string" })).toBe(""); expect(schemaExample(document, { format: "uuid", type: "string" }, 0, "model")).toBe( "123e4567-e89b-12d3-a456-426614174000", ); @@ -461,6 +462,41 @@ describe("Python generator", () => { pattern: "^b$", }), ).toEqual({ id: 1 }); + expect( + schemaExample(document, { + allOf: [ + { + anyOf: [ + { pattern: "^a$", type: "string" }, + { pattern: "^b$", type: "string" }, + ], + }, + { pattern: "^b$" }, + ], + }), + ).toBe("b"); + expect( + schemaExample(document, { + anyOf: [{ type: "null" }, { properties: { id: { type: "integer" } }, type: "object" }], + type: "object", + }), + ).toEqual({ id: 1 }); + expect( + schemaExample(document, { + allOf: [ + { items: { type: "string" }, type: "array" }, + { minItems: 2, type: "array" }, + ], + }), + ).toEqual(["example", "example"]); + expect( + schemaExample(document, { + allOf: [ + { items: { type: "string" }, type: "array" }, + { maxItems: 0, type: "array" }, + ], + }), + ).toEqual([]); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 34bd3f0..a442220 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -705,7 +705,7 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde function stringFormatMatches(value: string, format?: string): boolean { if (format === "date") return /^\d{4}-\d{2}-\d{2}$/.test(value); - if (format === "date-time") return !Number.isNaN(Date.parse(value)); + if (format === "date-time") return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value); if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); if (format === "uri" || format === "url") return URL.canParse(value); if (format === "hostname") @@ -826,12 +826,7 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam const flatten = (input: JsonSchema, depth = 0): JsonSchema[] => { const schema = resolveSchema(document, input) ?? input; if (depth > 8) return [schema]; - const union = schema.oneOf ?? schema.anyOf; - return [ - schema, - ...(schema.allOf ?? []).flatMap((item) => flatten(item, depth + 1)), - ...(union?.length ? flatten(union[0], depth + 1) : []), - ]; + return [schema, ...(schema.allOf ?? []).flatMap((item) => flatten(item, depth + 1))]; }; const schemas = inputs.flatMap((schema) => flatten(schema)); const result = Object.assign({}, ...schemas); @@ -854,6 +849,12 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam const maximumLengths = schemas.flatMap((schema) => (schema.maxLength === undefined ? [] : [schema.maxLength])); if (minimumLengths.length) result.minLength = Math.max(...minimumLengths); if (maximumLengths.length) result.maxLength = Math.min(...maximumLengths); + const minimumItems = schemas.flatMap((schema) => (schema.minItems === undefined ? [] : [schema.minItems])); + const maximumItems = schemas.flatMap((schema) => (schema.maxItems === undefined ? [] : [schema.maxItems])); + if (minimumItems.length) result.minItems = Math.max(...minimumItems); + if (maximumItems.length) result.maxItems = Math.min(...maximumItems); + const items = schemas.flatMap((schema) => (schema.items ? [schema.items] : [])); + if (items.length) result.items = items.length === 1 ? items[0] : { allOf: items }; const minimums = schemas.flatMap((schema) => typeof schema.exclusiveMinimum === "number" ? [{ exclusive: true, value: schema.exclusiveMinimum }] @@ -911,6 +912,47 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam return result; } +function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSchema, depth = 0): boolean { + if (depth > 8) return true; + const schema = resolveSchema(document, input) ?? input; + if (schema.const !== undefined && value !== schema.const) return false; + if (schema.enum?.length && !schema.enum.includes(value)) return false; + if (schema.allOf?.some((item) => !schemaMatches(document, value, item, depth + 1))) return false; + const union = schema.oneOf ?? schema.anyOf; + if (union?.length && !union.some((item) => schemaMatches(document, value, item, depth + 1))) return false; + const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []; + if ( + types.length && + !types.some((type) => { + if (type === "null") return value === null; + if (type === "array") return Array.isArray(value); + if (type === "object") return Boolean(value) && typeof value === "object" && !Array.isArray(value); + if (type === "integer") return typeof value === "number" && Number.isInteger(value); + return typeof value === type; + }) + ) + return false; + if ((typeof value === "string" || typeof value === "number") && !scalarMatches(value, schema)) return false; + 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))) + return false; + } + if (value && typeof value === "object" && !Array.isArray(value)) { + const record = value as Record; + if (schema.required?.some((name) => !Object.hasOwn(record, name))) return false; + if ( + Object.entries(schema.properties ?? {}).some( + ([name, property]) => + Object.hasOwn(record, name) && !schemaMatches(document, record[name], property, depth + 1), + ) + ) + return false; + } + return true; +} + export function schemaExample( document: OpenApiDocument, input: JsonSchema | undefined, @@ -930,24 +972,41 @@ export function schemaExample( for (const item of union) { const variant = resolveSchema(document, item) ?? item; const type = Array.isArray(variant.type) ? variant.type.find((value) => value !== "null") : variant.type; - if (variant.type === "null" || (Array.isArray(variant.type) && variant.type.every((value) => value === "null"))) - return null; + if (variant.type === "null" || (Array.isArray(variant.type) && variant.type.every((value) => value === "null"))) { + if (schemaMatches(document, null, schema)) return null; + continue; + } if (type === "object" || variant.properties) { - const selected = schemaExample(document, variant, depth + 1, name); - if (selected && typeof selected === "object" && !Array.isArray(selected) && schema.properties) { - const enclosing = schemaExample(document, siblings, depth + 1, name); - if (enclosing && typeof enclosing === "object" && !Array.isArray(enclosing)) - return { ...enclosing, ...selected }; - } - return selected; + const combined = objectSchema(document, { allOf: [siblings, variant] }); + const selected = schemaExample(document, combined ?? variant, depth + 1, name); + if (schemaMatches(document, selected, schema)) return selected; + continue; } const merged = mergeScalarSchemas(document, [variant, siblings], name); const selected = schemaExample(document, merged, depth + 1, name); - if (scalarMatches(selected, merged)) return selected; + if (schemaMatches(document, selected, schema)) return selected; } return schemaExample(document, union[0], depth + 1, name); } if (schema.allOf?.length) { + for (const [index, item] of schema.allOf.entries()) { + const nested = resolveSchema(document, item) ?? item; + const alternatives = nested.oneOf ?? nested.anyOf; + if (!alternatives?.length) continue; + for (const alternative of alternatives) { + const replacement = { ...nested, anyOf: undefined, oneOf: undefined, allOf: [alternative] }; + const candidate = schemaExample( + document, + { + ...schema, + allOf: schema.allOf.map((entry, entryIndex) => (entryIndex === index ? replacement : entry)), + }, + depth + 1, + name, + ); + if (schemaMatches(document, candidate, schema)) return candidate; + } + } const object = objectSchema(document, schema); if (object?.properties) return schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf], name), depth + 1, name); @@ -956,7 +1015,10 @@ export function schemaExample( const type = Array.isArray(schema.type) ? schema.type.find((value) => value !== "null") : schema.type; if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.every((value) => value === "null"))) return null; - if (type === "array") return [schemaExample(document, schema.items, depth + 1, name)]; + 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)); + } if (type === "boolean") return false; if (type === "integer" || type === "number") { const multiple = schema.multipleOf && schema.multipleOf > 0 ? schema.multipleOf : undefined; From 26ca8980515da59a0f1ef2fc583b481540664dbb Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:45:59 +0200 Subject: [PATCH 12/80] Preserve exclusive and nested unions --- lib/generators/python.test.ts | 15 +++++++++++++++ lib/openapi.ts | 22 +++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index ca00b07..8053cd4 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -497,6 +497,21 @@ describe("Python generator", () => { ], }), ).toEqual([]); + expect( + schemaExample(document, { + oneOf: [{ pattern: "^a$", type: "string" }, { type: "string" }], + }), + ).toBe("example"); + expect( + schemaExample(document, { + allOf: [ + { + allOf: [{ maxLength: 5 }], + anyOf: [{ pattern: "^abcde$|^yolo26n$", type: "string" }], + }, + ], + }), + ).toBe("abcde"); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index a442220..6eacb7e 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -768,8 +768,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const candidates = [ value, ...patterns.flatMap((pattern) => { - const match = pattern.match(/^\^([a-zA-Z0-9._-]+)\$$/); - return match?.[1] ? [match[1]] : []; + return pattern.split("|").flatMap((alternative) => { + const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); + return match?.[1] ? [match[1]] : []; + }); }), ...(schema.format === "email" ? ["a@b.co"] : []), ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), @@ -918,8 +920,13 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch if (schema.const !== undefined && value !== schema.const) return false; if (schema.enum?.length && !schema.enum.includes(value)) return false; if (schema.allOf?.some((item) => !schemaMatches(document, value, item, depth + 1))) return false; - const union = schema.oneOf ?? schema.anyOf; - if (union?.length && !union.some((item) => schemaMatches(document, value, item, depth + 1))) return false; + if ( + schema.oneOf?.length && + schema.oneOf.filter((item) => schemaMatches(document, value, item, depth + 1)).length !== 1 + ) + return false; + if (schema.anyOf?.length && !schema.anyOf.some((item) => schemaMatches(document, value, item, depth + 1))) + return false; const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []; if ( types.length && @@ -994,7 +1001,12 @@ export function schemaExample( const alternatives = nested.oneOf ?? nested.anyOf; if (!alternatives?.length) continue; for (const alternative of alternatives) { - const replacement = { ...nested, anyOf: undefined, oneOf: undefined, allOf: [alternative] }; + const replacement = { + ...nested, + anyOf: undefined, + oneOf: undefined, + allOf: [...(nested.allOf ?? []), alternative], + }; const candidate = schemaExample( document, { From 128227be1886ae9bb614182a9a46e2053f93a031 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:50:02 +0200 Subject: [PATCH 13/80] Enforce composed string and object constraints --- lib/generators/python.test.ts | 16 ++++++++++++++++ lib/openapi.ts | 15 ++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 8053cd4..22d7834 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -512,6 +512,22 @@ describe("Python generator", () => { ], }), ).toBe("abcde"); + expect( + schemaExample(document, { + allOf: [{ enum: ["a", "b"], type: "string" }, { pattern: "^b$" }], + }), + ).toBe("b"); + expect( + schemaExample(document, { + anyOf: [ + { additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }, + { additionalProperties: false, properties: { b: { type: "integer" } }, type: "object" }, + ], + properties: { b: { type: "integer" } }, + required: ["b"], + type: "object", + }), + ).toEqual({ b: 1 }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 6eacb7e..7e1429d 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -793,9 +793,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt } function scalarMatches(value: unknown, schema: JsonSchema): boolean { - const type = Array.isArray(schema.type) ? schema.type.filter((item) => item !== "null") : [schema.type]; - if (type.includes("string")) { - if (typeof value !== "string") return false; + const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []; + if (types.includes("string") && typeof value !== "string") return false; + if ((types.includes("number") || types.includes("integer")) && typeof value !== "number") return false; + if (typeof value === "string") { if (!stringFormatMatches(value, schema.format)) return false; if (schema.minLength !== undefined && value.length < schema.minLength) return false; if (schema.maxLength !== undefined && value.length > schema.maxLength) return false; @@ -807,8 +808,7 @@ function scalarMatches(value: unknown, schema: JsonSchema): boolean { } } } - if ((type.includes("number") || type.includes("integer")) && typeof value !== "number") return false; - if (type.includes("integer") && !Number.isInteger(value)) return false; + if (types.includes("integer") && !Number.isInteger(value)) return false; if (typeof value === "number") { const minimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum; const maximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum; @@ -949,6 +949,11 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch if (value && typeof value === "object" && !Array.isArray(value)) { const record = value as Record; if (schema.required?.some((name) => !Object.hasOwn(record, name))) return false; + if ( + schema.additionalProperties === false && + Object.keys(record).some((name) => !Object.hasOwn(schema.properties ?? {}, name)) + ) + return false; if ( Object.entries(schema.properties ?? {}).some( ([name, property]) => From 2840759f7e71f28fae95e84ce367ab4907333b6d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:55:16 +0200 Subject: [PATCH 14/80] Keep generated request union branches intact --- lib/generators/python.test.ts | 22 +++++++++++++++++++++ lib/openapi.ts | 36 +++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 22d7834..6e2a868 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -292,6 +292,24 @@ describe("Python generator", () => { }; const unionBody = requestBodyExample(minimalDocument, unionCreate); expect(unionBody).toBe('{\n "file": "path/to/file"\n}'); + const laterUnion = structuredClone(create); + laterUnion.requestBody = { + required: true, + content: { + "application/json": { + schema: { + anyOf: [ + { additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }, + { additionalProperties: false, properties: { b: { type: "integer" } }, type: "object" }, + ], + properties: { b: { type: "integer" } }, + required: ["b"], + type: "object", + }, + }, + }, + }; + expect(requestBodyExample(minimalDocument, laterUnion)).toBe('{\n "b": 1\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { description: "Upload a file Or Use a source URL", @@ -528,6 +546,10 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ b: 1 }); + expect(schemaExample(document, { enum: ["a", "b"], pattern: "^b$", type: "string" })).toBe("b"); + expect( + schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), + ).toEqual({}); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 7e1429d..2b71242 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -18,9 +18,11 @@ export interface JsonSchema { items?: JsonSchema; maxItems?: number; maxLength?: number; + maxProperties?: number; maximum?: number; minItems?: number; minLength?: number; + minProperties?: number; minimum?: number; multipleOf?: number; nullable?: boolean; @@ -948,6 +950,8 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch } if (value && typeof value === "object" && !Array.isArray(value)) { const record = value as Record; + if (schema.minProperties !== undefined && Object.keys(record).length < schema.minProperties) return false; + if (schema.maxProperties !== undefined && Object.keys(record).length > schema.maxProperties) return false; if (schema.required?.some((name) => !Object.hasOwn(record, name))) return false; if ( schema.additionalProperties === false && @@ -976,7 +980,10 @@ export function schemaExample( if (schema.example !== undefined) return schema.example; if (schema.default !== undefined) return schema.default; if (schema.const !== undefined) return schema.const; - if (schema.enum?.length) return schema.enum[0]; + if (schema.enum?.length) { + const siblings = { ...schema, enum: undefined }; + return schema.enum.find((value) => schemaMatches(document, value, siblings)) ?? schema.enum[0]; + } const union = schema.oneOf ?? schema.anyOf; if (union?.length) { @@ -1058,11 +1065,22 @@ export function schemaExample( return value; } if (type === "object" || schema.properties) { + const properties = Object.entries(schema.properties ?? {}); + const required = new Set(schema.required ?? []); + const selected = + schema.maxProperties === undefined + ? properties + : [ + ...properties.filter(([property]) => required.has(property)), + ...properties.filter(([property]) => !required.has(property)), + ].slice(0, schema.maxProperties); + const minimum = Math.min(schema.minProperties ?? 0, properties.length); + if (selected.length < minimum) { + const names = new Set(selected.map(([property]) => property)); + selected.push(...properties.filter(([property]) => !names.has(property)).slice(0, minimum - selected.length)); + } const example = Object.fromEntries( - Object.entries(schema.properties ?? {}).map(([name, property]) => [ - name, - schemaExample(document, property, depth + 1, name), - ]), + selected.map(([name, property]) => [name, schemaExample(document, property, depth + 1, name)]), ); if (Object.keys(example).length || typeof schema.additionalProperties !== "object") return example; return { key: schemaExample(document, schema.additionalProperties, depth + 1, "key") }; @@ -1250,17 +1268,19 @@ function authoredSchemaExample( function exampleObjectSchema( document: OpenApiDocument, input: JsonSchema | undefined, + value?: unknown, depth = 0, ): JsonSchema | undefined { if (depth > 8) return input; const schema = resolveSchema(document, input); if (!schema) return undefined; const union = schema.oneOf ?? schema.anyOf; + const selected = union?.find((item) => schemaMatches(document, value, item)) ?? union?.[0]; return objectSchema(document, { ...schema, allOf: [ - ...(schema.allOf ?? []).map((item) => exampleObjectSchema(document, item, depth + 1) ?? item), - ...(union?.length ? [exampleObjectSchema(document, union[0], depth + 1) ?? union[0]] : []), + ...(schema.allOf ?? []).map((item) => exampleObjectSchema(document, item, value, depth + 1) ?? item), + ...(selected ? [exampleObjectSchema(document, selected, value, depth + 1) ?? selected] : []), ], anyOf: undefined, oneOf: undefined, @@ -1277,7 +1297,7 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper const generated = !authored.found || authored.nested; let example = generated ? schemaExample(document, request[1].schema) : authored.value; if (generated && example && typeof example === "object" && !Array.isArray(example)) { - const object = exampleObjectSchema(document, request[1].schema); + const object = exampleObjectSchema(document, request[1].schema, example); example = { ...example }; const named = Object.entries(object?.properties ?? {}); const properties = named.filter(([, property]) => !resolveSchema(document, property)?.readOnly); From 2a676029691fe14c6891671489ab3c3370622400 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 03:59:34 +0200 Subject: [PATCH 15/80] Honor object bounds in generated requests --- lib/generators/python.test.ts | 35 +++++++++++++++++++++++++ lib/openapi.ts | 48 +++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 6e2a868..4fec76d 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -310,6 +310,12 @@ describe("Python generator", () => { }, }; expect(requestBodyExample(minimalDocument, laterUnion)).toBe('{\n "b": 1\n}'); + createMedia[1].schema = { + minProperties: 2, + properties: { a: { type: "integer" }, b: { type: "integer" } }, + type: "object", + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "a": 1,\n "b": 1\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { description: "Upload a file Or Use a source URL", @@ -550,6 +556,35 @@ describe("Python generator", () => { expect( schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), ).toEqual({}); + expect( + schemaExample(document, { + allOf: [ + { properties: { a: { type: "integer" } }, type: "object" }, + { maxProperties: 0, type: "object" }, + ], + }), + ).toEqual({}); + expect( + schemaExample(document, { additionalProperties: { type: "string" }, maxProperties: 0, type: "object" }), + ).toEqual({}); + expect( + schemaExample(document, { additionalProperties: { type: "string" }, minProperties: 2, type: "object" }), + ).toEqual({ key: "example-key", key2: "example-key" }); + expect( + schemaExample(document, { + anyOf: [ + { + additionalProperties: { type: "integer" }, + properties: { a: { type: "integer" } }, + type: "object", + }, + { additionalProperties: false, properties: { b: { type: "string" } }, type: "object" }, + ], + properties: { b: { type: "string" } }, + required: ["b"], + type: "object", + }), + ).toEqual({ b: "example-b" }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 2b71242..214dff0 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -664,10 +664,34 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde const schema = resolveSchema(document, input); if (!schema) return undefined; if (schema.allOf?.length) { - const objects = schema.allOf.map((item) => objectSchema(document, item)).filter((item) => item?.properties); + const objects = schema.allOf + .map((item) => objectSchema(document, item)) + .filter((item): item is JsonSchema => + Boolean( + item && + (item.type === "object" || + item.properties || + item.minProperties !== undefined || + item.maxProperties !== undefined || + item.additionalProperties !== undefined), + ), + ); if (objects.length) { + const composed = [schema, ...objects]; + const minimums = composed.flatMap((item) => (item.minProperties === undefined ? [] : [item.minProperties])); + const maximums = composed.flatMap((item) => (item.maxProperties === undefined ? [] : [item.maxProperties])); + const additional = composed.flatMap((item) => + typeof item.additionalProperties === "object" ? [item.additionalProperties] : [], + ); return { ...schema, + ...(composed.some((item) => item.additionalProperties === false) + ? { additionalProperties: false } + : additional.length + ? { additionalProperties: additional.length === 1 ? additional[0] : { allOf: additional } } + : {}), + ...(maximums.length ? { maxProperties: Math.min(...maximums) } : {}), + ...(minimums.length ? { minProperties: Math.max(...minimums) } : {}), properties: Object.assign({}, schema.properties, ...objects.map((item) => item?.properties)), required: [...new Set([...(schema.required ?? []), ...objects.flatMap((item) => item?.required ?? [])])], type: "object", @@ -958,6 +982,15 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch Object.keys(record).some((name) => !Object.hasOwn(schema.properties ?? {}, name)) ) return false; + if ( + typeof schema.additionalProperties === "object" && + Object.entries(record).some( + ([name, property]) => + !Object.hasOwn(schema.properties ?? {}, name) && + !schemaMatches(document, property, schema.additionalProperties as JsonSchema, depth + 1), + ) + ) + return false; if ( Object.entries(schema.properties ?? {}).some( ([name, property]) => @@ -1083,7 +1116,13 @@ export function schemaExample( selected.map(([name, property]) => [name, schemaExample(document, property, depth + 1, name)]), ); if (Object.keys(example).length || typeof schema.additionalProperties !== "object") return example; - return { key: schemaExample(document, schema.additionalProperties, depth + 1, "key") }; + const count = schema.maxProperties === 0 ? 0 : Math.max(1, schema.minProperties ?? 0); + return Object.fromEntries( + Array.from({ length: count }, (_, index) => [ + index ? `key${index + 1}` : "key", + schemaExample(document, schema.additionalProperties as JsonSchema, depth + 1, "key"), + ]), + ); } return stringExample(schema, name); } @@ -1305,6 +1344,11 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper const selected = properties.some(([name]) => required.has(name)) ? properties.filter(([name]) => required.has(name)) : properties.slice(0, 1); + const minimum = Math.min(object?.minProperties ?? 0, properties.length); + if (selected.length < minimum) { + const names = new Set(selected.map(([name]) => name)); + selected.push(...properties.filter(([name]) => !names.has(name)).slice(0, minimum - selected.length)); + } const values = example as Record; if (selected.length) example = Object.fromEntries(selected.map(([name]) => [name, values[name]])); else if (named.length) example = {}; From 6f91e796ebb172669cc4ce4a4be4744202e43f13 Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Thu, 13 Aug 2026 01:59:58 +0000 Subject: [PATCH 16/80] Auto-format by https://ultralytics.com/actions --- lib/openapi.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index 214dff0..a6487fe 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -669,11 +669,11 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde .filter((item): item is JsonSchema => Boolean( item && - (item.type === "object" || - item.properties || - item.minProperties !== undefined || - item.maxProperties !== undefined || - item.additionalProperties !== undefined), + (item.type === "object" || + item.properties || + item.minProperties !== undefined || + item.maxProperties !== undefined || + item.additionalProperties !== undefined), ), ); if (objects.length) { From 53dadeaf2b357417a50316673494809e924e70bd Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:01:37 +0200 Subject: [PATCH 17/80] Stabilize composed object formatting --- lib/openapi.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index a6487fe..74c06db 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -666,15 +666,14 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde if (schema.allOf?.length) { const objects = schema.allOf .map((item) => objectSchema(document, item)) - .filter((item): item is JsonSchema => - Boolean( - item && - (item.type === "object" || - item.properties || - item.minProperties !== undefined || - item.maxProperties !== undefined || - item.additionalProperties !== undefined), - ), + .filter( + (item): item is JsonSchema => + Boolean(item) && + (item?.type === "object" || + item?.properties !== undefined || + item?.minProperties !== undefined || + item?.maxProperties !== undefined || + item?.additionalProperties !== undefined), ); if (objects.length) { const composed = [schema, ...objects]; From 5cc321fee26ac4f1131c2316bda6e994d56e486e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:04:29 +0200 Subject: [PATCH 18/80] Preserve branch-local object constraints --- lib/generators/python.test.ts | 33 +++++++++++++++++++- lib/openapi.ts | 58 +++++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 4fec76d..1171d02 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -316,6 +316,13 @@ describe("Python generator", () => { type: "object", }; expect(requestBodyExample(minimalDocument, create)).toBe('{\n "a": 1,\n "b": 1\n}'); + createMedia[1].schema = { + additionalProperties: { type: "string" }, + minProperties: 2, + properties: { a: { type: "integer" } }, + type: "object", + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "a": 1,\n "key": "example-key"\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { description: "Upload a file Or Use a source URL", @@ -569,7 +576,7 @@ describe("Python generator", () => { ).toEqual({}); expect( schemaExample(document, { additionalProperties: { type: "string" }, minProperties: 2, type: "object" }), - ).toEqual({ key: "example-key", key2: "example-key" }); + ).toEqual({ key: "example-key", key2: "example-key2" }); expect( schemaExample(document, { anyOf: [ @@ -585,6 +592,30 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ b: "example-b" }); + expect( + schemaExample(document, { + allOf: [ + { properties: { a: { type: "integer" } }, type: "object" }, + { additionalProperties: false, properties: { b: { type: "integer" } }, type: "object" }, + ], + }), + ).toEqual({ b: 1 }); + expect(schemaExample(document, { anyOf: [{ type: ["string", "number"] }], pattern: "^a$" })).toBe("a"); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + properties: { a: { type: "integer" } }, + type: "object", + }), + ).toEqual({ a: 1, key: "example-key" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + required: ["foo"], + type: "object", + }), + ).toEqual({ foo: "example-foo" }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 74c06db..11828fd 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -819,8 +819,17 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt function scalarMatches(value: unknown, schema: JsonSchema): boolean { const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []; - if (types.includes("string") && typeof value !== "string") return false; - if ((types.includes("number") || types.includes("integer")) && typeof value !== "number") return false; + if ( + types.length && + !types.some( + (type) => + (type === "null" && value === null) || + (type === "integer" && typeof value === "number" && Number.isInteger(value)) || + (type === "number" && typeof value === "number") || + type === typeof value, + ) + ) + return false; if (typeof value === "string") { if (!stringFormatMatches(value, schema.format)) return false; if (schema.minLength !== undefined && value.length < schema.minLength) return false; @@ -1064,7 +1073,15 @@ export function schemaExample( } } const object = objectSchema(document, schema); - if (object?.properties) return schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); + if (object?.properties) { + const selected = schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); + if (schemaMatches(document, selected, schema)) return selected; + for (const item of schema.allOf) { + const candidate = schemaExample(document, item, depth + 1, name); + if (schemaMatches(document, candidate, schema)) return candidate; + } + return selected; + } return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf], name), depth + 1, name); } @@ -1111,17 +1128,23 @@ export function schemaExample( const names = new Set(selected.map(([property]) => property)); selected.push(...properties.filter(([property]) => !names.has(property)).slice(0, minimum - selected.length)); } - const example = Object.fromEntries( + const values = new Map( selected.map(([name, property]) => [name, schemaExample(document, property, depth + 1, name)]), ); - if (Object.keys(example).length || typeof schema.additionalProperties !== "object") return example; - const count = schema.maxProperties === 0 ? 0 : Math.max(1, schema.minProperties ?? 0); - return Object.fromEntries( - Array.from({ length: count }, (_, index) => [ - index ? `key${index + 1}` : "key", - schemaExample(document, schema.additionalProperties as JsonSchema, depth + 1, "key"), - ]), - ); + const additional = + typeof schema.additionalProperties === "object" ? schema.additionalProperties : { type: "string" }; + for (const property of required) { + if (!values.has(property)) values.set(property, schemaExample(document, additional, depth + 1, property)); + } + const target = + schema.maxProperties === 0 + ? 0 + : Math.max(schema.minProperties ?? 0, values.size || (typeof schema.additionalProperties === "object" ? 1 : 0)); + for (let index = 1; values.size < target; index += 1) { + const property = index === 1 ? "key" : `key${index}`; + if (!values.has(property)) values.set(property, schemaExample(document, additional, depth + 1, property)); + } + return Object.fromEntries(values); } return stringExample(schema, name); } @@ -1349,6 +1372,17 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper selected.push(...properties.filter(([name]) => !names.has(name)).slice(0, minimum - selected.length)); } const values = example as Record; + const selectedNames = new Set(selected.map(([name]) => name)); + for (const name of required) { + if (Object.hasOwn(values, name) && !selectedNames.has(name)) { + selected.push([name, {}]); + selectedNames.add(name); + } + } + for (const name of Object.keys(values)) { + if (selected.length >= (object?.minProperties ?? 0)) break; + if (!selectedNames.has(name)) selected.push([name, {}]); + } if (selected.length) example = Object.fromEntries(selected.map(([name]) => [name, values[name]])); else if (named.length) example = {}; } From 4b338cef4ea2d280349dd91d5e3580ac549e3338 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:09:56 +0200 Subject: [PATCH 19/80] Respect dynamic keys and writable fields --- lib/generators/python.test.ts | 20 ++++++++++++++ lib/openapi.ts | 50 ++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 1171d02..dba4e88 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -323,6 +323,13 @@ describe("Python generator", () => { type: "object", }; expect(requestBodyExample(minimalDocument, create)).toBe('{\n "a": 1,\n "key": "example-key"\n}'); + createMedia[1].schema = { + maxProperties: 1, + properties: { id: { readOnly: true, type: "string" } }, + required: ["id"], + type: "object", + }; + expect(requestBodyExample(minimalDocument, create)).toBe("{}"); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { description: "Upload a file Or Use a source URL", @@ -616,6 +623,19 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ foo: "example-foo" }); + expect( + schemaExample(document, { + allOf: [{ type: ["string", "number"] }, { minimum: 1, type: "number" }], + }), + ).toBe(1); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 1, + propertyNames: { pattern: "^[A-Z]+$", type: "string" }, + type: "object", + }), + ).toEqual({ KEY: "example-key" }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 11828fd..10c4774 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -803,6 +803,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(key === "model" ? ["yolo26n", "yolo26"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), + ...(patterns.some((pattern) => pattern.includes("[A-Z]+")) ? ["KEY"] : []), "example", ].map(constrainLength); return ( @@ -870,16 +871,18 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam delete result.allOf; delete result.anyOf; delete result.oneOf; - const types = schemas.flatMap((schema) => { - const values = Array.isArray(schema.type) ? schema.type : [schema.type]; - return values.filter((value): value is string => Boolean(value) && value !== "null"); + const typeSets = schemas.flatMap((schema) => { + const values = (Array.isArray(schema.type) ? schema.type : [schema.type]).filter( + (value): value is string => Boolean(value) && value !== "null", + ); + return values.length + ? [new Set(values.flatMap((value) => (value === "number" ? ["integer", "number"] : [value])))] + : []; }); - if (types.length) { - const unique = new Set(types); - result.type = - unique.has("integer") && [...unique].every((value) => value === "integer" || value === "number") - ? "integer" - : types[0]; + if (typeSets.length) { + const types = [...typeSets[0]].filter((value) => typeSets.every((items) => items.has(value))); + if (types.includes("number") && types.includes("integer")) types.splice(types.indexOf("integer"), 1); + result.type = types.length === 1 ? types[0] : types; } const minimumLengths = schemas.flatMap((schema) => (schema.minLength === undefined ? [] : [schema.minLength])); const maximumLengths = schemas.flatMap((schema) => (schema.maxLength === undefined ? [] : [schema.maxLength])); @@ -985,6 +988,11 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch if (schema.minProperties !== undefined && Object.keys(record).length < schema.minProperties) return false; if (schema.maxProperties !== undefined && Object.keys(record).length > schema.maxProperties) return false; if (schema.required?.some((name) => !Object.hasOwn(record, name))) return false; + if ( + schema.propertyNames && + Object.keys(record).some((name) => !schemaMatches(document, name, schema.propertyNames as JsonSchema, depth + 1)) + ) + return false; if ( schema.additionalProperties === false && Object.keys(record).some((name) => !Object.hasOwn(schema.properties ?? {}, name)) @@ -1140,8 +1148,17 @@ export function schemaExample( schema.maxProperties === 0 ? 0 : Math.max(schema.minProperties ?? 0, values.size || (typeof schema.additionalProperties === "object" ? 1 : 0)); + const key = + !schema.propertyNames || schemaMatches(document, "key", schema.propertyNames) + ? "key" + : String(schemaExample(document, schema.propertyNames, depth + 1, "key")); for (let index = 1; values.size < target; index += 1) { - const property = index === 1 ? "key" : `key${index}`; + const property = + index === 1 + ? key + : ([`${key}${index}`, `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`].find( + (candidate) => !schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames), + ) ?? `${key}${index}`); if (!values.has(property)) values.set(property, schemaExample(document, additional, depth + 1, property)); } return Object.fromEntries(values); @@ -1374,15 +1391,24 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper const values = example as Record; const selectedNames = new Set(selected.map(([name]) => name)); for (const name of required) { - if (Object.hasOwn(values, name) && !selectedNames.has(name)) { + const property = object?.properties?.[name]; + if ( + Object.hasOwn(values, name) && + !selectedNames.has(name) && + (!property || !resolveSchema(document, property)?.readOnly) + ) { selected.push([name, {}]); selectedNames.add(name); } } for (const name of Object.keys(values)) { if (selected.length >= (object?.minProperties ?? 0)) break; - if (!selectedNames.has(name)) selected.push([name, {}]); + const property = object?.properties?.[name]; + if (!selectedNames.has(name) && (!property || !resolveSchema(document, property)?.readOnly)) { + selected.push([name, {}]); + } } + if (object?.maxProperties !== undefined) selected.splice(object.maxProperties); if (selected.length) example = Object.fromEntries(selected.map(([name]) => [name, values[name]])); else if (named.length) example = {}; } From ff01f0b4ea7d3f5b728fcacdaf48450ddd2adf42 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:14:33 +0200 Subject: [PATCH 20/80] Validate intersected types and property names --- lib/generators/python.test.ts | 23 ++++++++++++++++++++++- lib/openapi.ts | 22 ++++++++++++++++------ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index dba4e88..9b2c89e 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -452,7 +452,7 @@ describe("Python generator", () => { ); expect(schemaExample(document, { type: "string" }, 0, "project")).toBe("example-project"); expect(schemaExample(document, { maxLength: 2, type: "string" }, 0, "iconLetter")).toBe("ex"); - expect(schemaExample(document, { pattern: "^[A-Z]{8}$", type: "string" }, 0, "code")).toBe(""); + expect(schemaExample(document, { pattern: "^[A-Z]{8}$", type: "string" }, 0, "code")).toBe("AAAAAAAA"); expect(schemaExample(document, { pattern: "^[a-z0-9]+$", type: "string" }, 0, "model")).toBe("yolo26n"); expect(schemaExample(document, { pattern: "^[a-z]+$", type: "string" }, 0, "model")).toBe("example"); expect(schemaExample(document, { maxLength: 6, pattern: "^yolo26n$|^yolo26$", type: "string" }, 0, "model")).toBe( @@ -636,6 +636,27 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ KEY: "example-key" }); + expect( + schemaExample(document, { + anyOf: [{ maximum: 1.9, minimum: 1.5, type: ["integer", "number"] }], + }), + ).toBe(1.5); + expect( + schemaExample(document, { + allOf: [ + { additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }, + { additionalProperties: false, properties: { b: { type: "integer" } }, type: "object" }, + ], + }), + ).toEqual({}); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 1, + propertyNames: { pattern: "^[A-Z]{8}$", type: "string" }, + type: "object", + }), + ).toEqual({ AAAAAAAA: "example-aaaaaaaa" }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 10c4774..72d218c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -793,6 +793,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const candidates = [ value, ...patterns.flatMap((pattern) => { + const repeated = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\$$/); + if (repeated?.[1] && repeated[2]) return [repeated[1].toUpperCase().repeat(Number(repeated[2]))]; return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); return match?.[1] ? [match[1]] : []; @@ -843,7 +845,7 @@ function scalarMatches(value: unknown, schema: JsonSchema): boolean { } } } - if (types.includes("integer") && !Number.isInteger(value)) return false; + if (types.includes("integer") && !types.includes("number") && !Number.isInteger(value)) return false; if (typeof value === "number") { const minimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum; const maximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum; @@ -1088,6 +1090,7 @@ export function schemaExample( const candidate = schemaExample(document, item, depth + 1, name); if (schemaMatches(document, candidate, schema)) return candidate; } + if (schemaMatches(document, {}, schema)) return {}; return selected; } return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf], name), depth + 1, name); @@ -1153,12 +1156,19 @@ export function schemaExample( ? "key" : String(schemaExample(document, schema.propertyNames, depth + 1, "key")); for (let index = 1; values.size < target; index += 1) { - const property = + const candidates = index === 1 - ? key - : ([`${key}${index}`, `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`].find( - (candidate) => !schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames), - ) ?? `${key}${index}`); + ? [key] + : [ + `${key}${index}`, + `${key}${"X".repeat(index - 1)}`, + `${key}${"x".repeat(index - 1)}`, + `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, + ]; + const property = candidates.find( + (candidate) => !schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames), + ); + if (!property) break; if (!values.has(property)) values.set(property, schemaExample(document, additional, depth + 1, property)); } return Object.fromEntries(values); From 3b26eef56c7b10beed61f78596cfb665cdcce032 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:20:16 +0200 Subject: [PATCH 21/80] Preserve object and scalar composition bounds --- lib/generators/python.test.ts | 12 +++++ lib/openapi.ts | 87 ++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 9b2c89e..088f50d 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -330,6 +330,10 @@ describe("Python generator", () => { type: "object", }; expect(requestBodyExample(minimalDocument, create)).toBe("{}"); + createMedia[1].schema = { + allOf: [{ properties: { a: { type: "integer" }, b: { type: "integer" } }, type: "object" }, { required: ["b"] }], + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "b": 1\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { description: "Upload a file Or Use a source URL", @@ -446,6 +450,13 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "ipv4", type: "string" })).toBe("192.0.2.1"); expect(schemaExample(document, { format: "custom", type: "string" })).toBe(""); expect(schemaExample(document, { format: "date", type: "string" }, 0, "data")).toBe("2026-01-01"); + expect( + schemaExample(document, { + format: "date", + pattern: "^2026-02-30$|^2027-01-01$", + type: "string", + }), + ).toBe("2027-01-01"); expect(schemaExample(document, { format: "date-time", maxLength: 10, type: "string" })).toBe(""); expect(schemaExample(document, { format: "uuid", type: "string" }, 0, "model")).toBe( "123e4567-e89b-12d3-a456-426614174000", @@ -657,6 +668,7 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ AAAAAAAA: "example-aaaaaaaa" }); + expect(schemaExample(document, { exclusiveMinimum: 1, minimum: 5, type: "number" })).toBe(5); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 72d218c..805c6e6 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -671,9 +671,11 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde Boolean(item) && (item?.type === "object" || item?.properties !== undefined || + Boolean(item?.required?.length) || item?.minProperties !== undefined || item?.maxProperties !== undefined || - item?.additionalProperties !== undefined), + item?.additionalProperties !== undefined || + item?.propertyNames !== undefined), ); if (objects.length) { const composed = [schema, ...objects]; @@ -729,8 +731,23 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde } function stringFormatMatches(value: string, format?: string): boolean { - if (format === "date") return /^\d{4}-\d{2}-\d{2}$/.test(value); - if (format === "date-time") return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value); + const dateValid = (date: string) => { + if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return false; + const parsed = new Date(`${date}T00:00:00Z`); + return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date; + }; + if (format === "date") return dateValid(value); + if (format === "date-time") { + const match = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/); + return Boolean( + match?.[1] && + dateValid(match[1]) && + Number(match[2]) <= 23 && + Number(match[3]) <= 59 && + Number(match[4]) <= 59 && + !Number.isNaN(Date.parse(value)), + ); + } if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); if (format === "uri" || format === "url") return URL.canParse(value); if (format === "hostname") @@ -847,12 +864,18 @@ function scalarMatches(value: unknown, schema: JsonSchema): boolean { } if (types.includes("integer") && !types.includes("number") && !Number.isInteger(value)) return false; if (typeof value === "number") { - const minimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum; - const maximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum; - const minimumExclusive = typeof schema.exclusiveMinimum === "number" || schema.exclusiveMinimum === true; - const maximumExclusive = typeof schema.exclusiveMaximum === "number" || schema.exclusiveMaximum === true; - if (minimum !== undefined && (value < minimum || (minimumExclusive && value <= minimum))) return false; - if (maximum !== undefined && (value > maximum || (maximumExclusive && value >= maximum))) return false; + if ( + schema.minimum !== undefined && + (value < schema.minimum || (schema.exclusiveMinimum === true && value <= schema.minimum)) + ) + return false; + if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum) return false; + if ( + schema.maximum !== undefined && + (value > schema.maximum || (schema.exclusiveMaximum === true && value >= schema.maximum)) + ) + return false; + if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) return false; if (schema.multipleOf && Math.abs(value / schema.multipleOf - Math.round(value / schema.multipleOf)) > 1e-9) return false; } else if (schema.multipleOf !== undefined || schema.minimum !== undefined || schema.maximum !== undefined) { @@ -896,20 +919,14 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam if (maximumItems.length) result.maxItems = Math.min(...maximumItems); const items = schemas.flatMap((schema) => (schema.items ? [schema.items] : [])); if (items.length) result.items = items.length === 1 ? items[0] : { allOf: items }; - const minimums = schemas.flatMap((schema) => - typeof schema.exclusiveMinimum === "number" - ? [{ exclusive: true, value: schema.exclusiveMinimum }] - : schema.minimum === undefined - ? [] - : [{ exclusive: schema.exclusiveMinimum === true, value: schema.minimum }], - ); - const maximums = schemas.flatMap((schema) => - typeof schema.exclusiveMaximum === "number" - ? [{ exclusive: true, value: schema.exclusiveMaximum }] - : schema.maximum === undefined - ? [] - : [{ exclusive: schema.exclusiveMaximum === true, value: schema.maximum }], - ); + const minimums = schemas.flatMap((schema) => [ + ...(schema.minimum === undefined ? [] : [{ exclusive: schema.exclusiveMinimum === true, value: schema.minimum }]), + ...(typeof schema.exclusiveMinimum === "number" ? [{ exclusive: true, value: schema.exclusiveMinimum }] : []), + ]); + const maximums = schemas.flatMap((schema) => [ + ...(schema.maximum === undefined ? [] : [{ exclusive: schema.exclusiveMaximum === true, value: schema.maximum }]), + ...(typeof schema.exclusiveMaximum === "number" ? [{ exclusive: true, value: schema.exclusiveMaximum }] : []), + ]); const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; delete result.minimum; @@ -1107,19 +1124,25 @@ export function schemaExample( if (type === "integer" || type === "number") { const multiple = schema.multipleOf && schema.multipleOf > 0 ? schema.multipleOf : undefined; const step = multiple ?? 1; - const minimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum; - const maximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum; - const minimumExclusive = typeof schema.exclusiveMinimum === "number" || schema.exclusiveMinimum === true; - const maximumExclusive = typeof schema.exclusiveMaximum === "number" || schema.exclusiveMaximum === true; + const minimums = [ + ...(schema.minimum === undefined ? [] : [{ exclusive: schema.exclusiveMinimum === true, value: schema.minimum }]), + ...(typeof schema.exclusiveMinimum === "number" ? [{ exclusive: true, value: schema.exclusiveMinimum }] : []), + ]; + const maximums = [ + ...(schema.maximum === undefined ? [] : [{ exclusive: schema.exclusiveMaximum === true, value: schema.maximum }]), + ...(typeof schema.exclusiveMaximum === "number" ? [{ exclusive: true, value: schema.exclusiveMaximum }] : []), + ]; + const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; + const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; let value = 1; - if (minimum !== undefined && (value < minimum || (minimumExclusive && value <= minimum))) { - value = minimum + (minimumExclusive ? step : 0); + if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { + value = minimum.value + (minimum.exclusive ? step : 0); } if (type === "integer") value = Math.ceil(value); if (multiple) value = Math.ceil(value / multiple) * multiple; - if (maximum !== undefined && (value > maximum || (maximumExclusive && value >= maximum))) { - value = maximum - (maximumExclusive ? step : 0); - if (type === "integer") value = maximumExclusive ? Math.ceil(maximum) - 1 : Math.floor(maximum); + if (maximum && (value > maximum.value || (maximum.exclusive && value >= maximum.value))) { + value = maximum.value - (maximum.exclusive ? step : 0); + if (type === "integer") value = maximum.exclusive ? Math.ceil(maximum.value) - 1 : Math.floor(maximum.value); if (multiple) value = Math.floor(value / multiple) * multiple; } return value; From 653ecde5aeabeef0dddc7fa9594ff8d308184342 Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Thu, 13 Aug 2026 02:20:43 +0000 Subject: [PATCH 22/80] Auto-format by https://ultralytics.com/actions --- lib/openapi.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index 805c6e6..9852ad9 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -741,11 +741,11 @@ function stringFormatMatches(value: string, format?: string): boolean { const match = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/); return Boolean( match?.[1] && - dateValid(match[1]) && - Number(match[2]) <= 23 && - Number(match[3]) <= 59 && - Number(match[4]) <= 59 && - !Number.isNaN(Date.parse(value)), + dateValid(match[1]) && + Number(match[2]) <= 23 && + Number(match[3]) <= 59 && + Number(match[4]) <= 59 && + !Number.isNaN(Date.parse(value)), ); } if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); From 8ec0c2cfd16190f4ad68280ea1da4aee3bafc7fd Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:26:15 +0200 Subject: [PATCH 23/80] Fix constrained schema examples --- lib/generators/python.test.ts | 27 +++++++++++++++++ lib/openapi.ts | 56 +++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 088f50d..0ae1c6a 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -708,6 +708,33 @@ describe("Python generator", () => { }), ).toBe(1.2); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); + expect( + schemaExample(document, { + exclusiveMaximum: 0.25, + exclusiveMinimum: 0.15, + multipleOf: 0.1, + type: "number", + }), + ).toBe(0.2); + expect( + schemaExample(document, { + allOf: [{ type: ["string", "null"] }, { type: "null" }], + }), + ).toBeNull(); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 1, + propertyNames: { pattern: "^[0-9]{3}$", type: "string" }, + type: "object", + }), + ).toEqual({ "000": "example-000" }); + expect(schemaExample(document, { format: "time", pattern: "^99:99:99Z$|^12:00:00Z$", type: "string" })).toBe( + "12:00:00Z", + ); + expect(schemaExample(document, { format: "ipv6", pattern: "^::::$|^2001:db8::1$", type: "string" })).toBe( + "2001:db8::1", + ); expect( schemaExample(document, { exclusiveMaximum: true, diff --git a/lib/openapi.ts b/lib/openapi.ts index 9852ad9..613c391 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -741,11 +741,11 @@ function stringFormatMatches(value: string, format?: string): boolean { const match = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/); return Boolean( match?.[1] && - dateValid(match[1]) && - Number(match[2]) <= 23 && - Number(match[3]) <= 59 && - Number(match[4]) <= 59 && - !Number.isNaN(Date.parse(value)), + dateValid(match[1]) && + Number(match[2]) <= 23 && + Number(match[3]) <= 59 && + Number(match[4]) <= 59 && + !Number.isNaN(Date.parse(value)), ); } if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); @@ -758,8 +758,23 @@ function stringFormatMatches(value: string, format?: string): boolean { return ( value.split(".").length === 4 && value.split(".").every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255) ); - if (format === "ipv6") return value.includes(":") && /^[0-9a-f:]+$/i.test(value); - if (format === "time") return /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value); + if (format === "ipv6") { + const halves = value.split("::"); + if (halves.length > 2) return false; + const groups = halves.flatMap((half) => (half ? half.split(":") : [])); + if (groups.some((group) => !/^[0-9a-f]{1,4}$/i.test(group))) return false; + return halves.length === 2 ? groups.length < 8 : groups.length === 8; + } + if (format === "time") { + const match = value.match(/^(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-](\d{2}):(\d{2}))$/); + return Boolean( + match && + Number(match[1]) <= 23 && + Number(match[2]) <= 59 && + Number(match[3]) <= 59 && + (!match[5] || (Number(match[5]) <= 23 && Number(match[6]) <= 59)), + ); + } if (format === "duration") return /^P(?=\d|T\d)/.test(value); if (format === "uuid") return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); @@ -810,8 +825,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const candidates = [ value, ...patterns.flatMap((pattern) => { - const repeated = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\$$/); - if (repeated?.[1] && repeated[2]) return [repeated[1].toUpperCase().repeat(Number(repeated[2]))]; + const repeated = pattern.match(/^\^\[([A-Za-z0-9])-([A-Za-z0-9])\]\{(\d+)\}\$$/); + if (repeated?.[1] && repeated[3]) return [repeated[1].toUpperCase().repeat(Number(repeated[3]))]; return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); return match?.[1] ? [match[1]] : []; @@ -897,8 +912,8 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam delete result.anyOf; delete result.oneOf; const typeSets = schemas.flatMap((schema) => { - const values = (Array.isArray(schema.type) ? schema.type : [schema.type]).filter( - (value): value is string => Boolean(value) && value !== "null", + const values = (Array.isArray(schema.type) ? schema.type : [schema.type]).filter((value): value is string => + Boolean(value), ); return values.length ? [new Set(values.flatMap((value) => (value === "number" ? ["integer", "number"] : [value])))] @@ -1134,16 +1149,27 @@ export function schemaExample( ]; const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; + const round = (candidate: number) => Number(candidate.toPrecision(15)); let value = 1; if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { - value = minimum.value + (minimum.exclusive ? step : 0); + value = minimum.value; + if (type === "integer") value = Math.ceil(value); + if (multiple) value = round(Math.ceil(value / multiple) * multiple); + if (minimum.exclusive && value <= minimum.value) value = round(value + step); + } else { + if (type === "integer") value = Math.ceil(value); + if (multiple) value = round(Math.ceil(value / multiple) * multiple); } - if (type === "integer") value = Math.ceil(value); - if (multiple) value = Math.ceil(value / multiple) * multiple; if (maximum && (value > maximum.value || (maximum.exclusive && value >= maximum.value))) { value = maximum.value - (maximum.exclusive ? step : 0); if (type === "integer") value = maximum.exclusive ? Math.ceil(maximum.value) - 1 : Math.floor(maximum.value); - if (multiple) value = Math.floor(value / multiple) * multiple; + if (multiple) value = round(Math.floor(value / multiple) * multiple); + } + if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { + value = minimum.value; + if (type === "integer") value = Math.ceil(value); + if (multiple) value = round(Math.ceil(value / multiple) * multiple); + if (minimum.exclusive && value <= minimum.value) value = round(value + step); } return value; } From a64518ffb742c4ce4a5926796bc6c3f6e14432bc Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Thu, 13 Aug 2026 02:26:38 +0000 Subject: [PATCH 24/80] Auto-format by https://ultralytics.com/actions --- lib/openapi.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index 613c391..1410e79 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -741,11 +741,11 @@ function stringFormatMatches(value: string, format?: string): boolean { const match = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/); return Boolean( match?.[1] && - dateValid(match[1]) && - Number(match[2]) <= 23 && - Number(match[3]) <= 59 && - Number(match[4]) <= 59 && - !Number.isNaN(Date.parse(value)), + dateValid(match[1]) && + Number(match[2]) <= 23 && + Number(match[3]) <= 59 && + Number(match[4]) <= 59 && + !Number.isNaN(Date.parse(value)), ); } if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); @@ -769,10 +769,10 @@ function stringFormatMatches(value: string, format?: string): boolean { const match = value.match(/^(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-](\d{2}):(\d{2}))$/); return Boolean( match && - Number(match[1]) <= 23 && - Number(match[2]) <= 59 && - Number(match[3]) <= 59 && - (!match[5] || (Number(match[5]) <= 23 && Number(match[6]) <= 59)), + Number(match[1]) <= 23 && + Number(match[2]) <= 59 && + Number(match[3]) <= 59 && + (!match[5] || (Number(match[5]) <= 23 && Number(match[6]) <= 59)), ); } if (format === "duration") return /^P(?=\d|T\d)/.test(value); From ccbf3d025333129314c1733aa34d69bee605ca5a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:29:02 +0200 Subject: [PATCH 25/80] Stabilize shared formatter output --- lib/openapi.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index 1410e79..188fab9 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -739,13 +739,13 @@ function stringFormatMatches(value: string, format?: string): boolean { if (format === "date") return dateValid(value); if (format === "date-time") { const match = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/); - return Boolean( - match?.[1] && + if (!match?.[1]) return false; + return ( dateValid(match[1]) && Number(match[2]) <= 23 && Number(match[3]) <= 59 && Number(match[4]) <= 59 && - !Number.isNaN(Date.parse(value)), + !Number.isNaN(Date.parse(value)) ); } if (format === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value); @@ -767,12 +767,12 @@ function stringFormatMatches(value: string, format?: string): boolean { } if (format === "time") { const match = value.match(/^(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-](\d{2}):(\d{2}))$/); - return Boolean( - match && + if (!match) return false; + return ( Number(match[1]) <= 23 && Number(match[2]) <= 59 && Number(match[3]) <= 59 && - (!match[5] || (Number(match[5]) <= 23 && Number(match[6]) <= 59)), + (!match[5] || (Number(match[5]) <= 23 && Number(match[6]) <= 59)) ); } if (format === "duration") return /^P(?=\d|T\d)/.test(value); From cd6d23a5ce2c4d820a13b0d3ad505335e282aa0f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:31:01 +0200 Subject: [PATCH 26/80] Complete constrained example coverage --- lib/generators/python.test.ts | 16 ++++++++++++++++ lib/openapi.ts | 14 +++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 0ae1c6a..c61f1f2 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -735,6 +735,22 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "ipv6", pattern: "^::::$|^2001:db8::1$", type: "string" })).toBe( "2001:db8::1", ); + expect(schemaExample(document, { format: "duration", pattern: "^P1$", type: "string" })).toBe(""); + expect( + schemaExample(document, { + minimum: 10, + pattern: "^a$", + type: ["string", "number"], + }), + ).toBe("a"); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 1, + propertyNames: { pattern: "^[a-z]{3}[0-9]+$", type: "string" }, + type: "object", + }), + ).toEqual({ aaa0: "example-aaa0" }); expect( schemaExample(document, { exclusiveMaximum: true, diff --git a/lib/openapi.ts b/lib/openapi.ts index 188fab9..62364dc 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -775,7 +775,10 @@ function stringFormatMatches(value: string, format?: string): boolean { (!match[5] || (Number(match[5]) <= 23 && Number(match[6]) <= 59)) ); } - if (format === "duration") return /^P(?=\d|T\d)/.test(value); + if (format === "duration") + return /^P(?=\d|T\d)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?S)?)?$/.test( + value, + ); if (format === "uuid") return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); return !format || format === "binary" || format === "byte" || format === "password"; @@ -825,8 +828,11 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const candidates = [ value, ...patterns.flatMap((pattern) => { - const repeated = pattern.match(/^\^\[([A-Za-z0-9])-([A-Za-z0-9])\]\{(\d+)\}\$$/); - if (repeated?.[1] && repeated[3]) return [repeated[1].toUpperCase().repeat(Number(repeated[3]))]; + const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; + const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)\})/g)] : []; + if (sequence?.includes("{") || ranges.length > 1) { + return [ranges.map((match) => match[1]?.repeat(match[3] ? Number(match[3]) : 1)).join("")]; + } return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); return match?.[1] ? [match[1]] : []; @@ -893,8 +899,6 @@ function scalarMatches(value: unknown, schema: JsonSchema): boolean { if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) return false; if (schema.multipleOf && Math.abs(value / schema.multipleOf - Math.round(value / schema.multipleOf)) > 1e-9) return false; - } else if (schema.multipleOf !== undefined || schema.minimum !== undefined || schema.maximum !== undefined) { - return false; } return true; } From 7cb9ff3e47ad40f10f32f78659178560f9648e65 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:34:53 +0200 Subject: [PATCH 27/80] Validate composed schema fallbacks --- lib/generators/python.test.ts | 33 +++++++++++++++++++++++++++++++++ lib/openapi.ts | 28 ++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index c61f1f2..a2ecee4 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -751,6 +751,39 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ aaa0: "example-aaa0" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 1, + propertyNames: { pattern: "^(ab|cd)$", type: "string" }, + type: "object", + }), + ).toEqual({ ab: "example-ab" }); + expect( + schemaExample(document, { + allOf: [ + { format: "date", type: "string" }, + { format: "password", type: "string" }, + ], + }), + ).toBe("2026-01-01"); + expect( + schemaExample(document, { + allOf: [ + { + additionalProperties: false, + properties: { a: { type: "integer" }, b: { type: "integer" } }, + type: "object", + }, + { + additionalProperties: false, + properties: { b: { type: "integer" }, c: { type: "integer" } }, + required: ["b"], + type: "object", + }, + ], + }), + ).toEqual({ b: 1 }); expect( schemaExample(document, { exclusiveMaximum: true, diff --git a/lib/openapi.ts b/lib/openapi.ts index 62364dc..1421ce0 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -828,6 +828,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const candidates = [ value, ...patterns.flatMap((pattern) => { + const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; + if (grouped) return grouped.split("|"); const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)\})/g)] : []; if (sequence?.includes("{") || ranges.length > 1) { @@ -1126,10 +1128,32 @@ export function schemaExample( const candidate = schemaExample(document, item, depth + 1, name); if (schemaMatches(document, candidate, schema)) return candidate; } + const required = new Set(object.required ?? []); + const closed = schema.allOf.flatMap((item) => { + const branch = objectSchema(document, item); + return branch?.additionalProperties === false ? [new Set(Object.keys(branch.properties ?? {}))] : []; + }); + const common = Object.fromEntries( + Object.entries(selected as Record).filter( + ([property]) => required.has(property) || closed.every((properties) => properties.has(property)), + ), + ); + if (schemaMatches(document, common, schema)) return common; if (schemaMatches(document, {}, schema)) return {}; - return selected; + return null; } - return schemaExample(document, mergeScalarSchemas(document, [schema, ...schema.allOf], name), depth + 1, name); + const selected = schemaExample( + document, + mergeScalarSchemas(document, [schema, ...schema.allOf], name), + depth + 1, + name, + ); + if (schemaMatches(document, selected, schema)) return selected; + for (const item of schema.allOf) { + const candidate = schemaExample(document, item, depth + 1, name); + if (schemaMatches(document, candidate, schema)) return candidate; + } + return null; } const type = Array.isArray(schema.type) ? schema.type.find((value) => value !== "null") : schema.type; From 4c421cdb3fa309b8a6b9a2c86e7c8e02ca513eda Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:39:22 +0200 Subject: [PATCH 28/80] Select valid composed union examples --- lib/generators/python.test.ts | 22 ++++++++++++++++++++++ lib/openapi.ts | 9 +++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a2ecee4..c305aec 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -577,6 +577,28 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ b: 1 }); + expect( + schemaExample(document, { + const: null, + oneOf: [{ nullable: true, type: "string" }, { type: "number" }], + }), + ).toBeNull(); + expect(schemaExample(document, { oneOf: [{}, { type: "string" }] })).toBeNull(); + expect( + schemaExample(document, { + allOf: [ + { + properties: { failureId: { type: "string" } }, + required: ["failureId"], + type: "object", + }, + { + oneOf: [{ required: ["error"] }, { required: ["retryAfter"] }], + properties: { error: { type: "string" }, retryAfter: { type: "integer" } }, + }, + ], + }), + ).toEqual({ error: "example-error", failureId: "resource-id" }); expect(schemaExample(document, { enum: ["a", "b"], pattern: "^b$", type: "string" })).toBe("b"); expect( schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), diff --git a/lib/openapi.ts b/lib/openapi.ts index 1421ce0..bdb14c8 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1007,6 +1007,7 @@ function schemaMatches(document: OpenApiDocument, value: unknown, input: JsonSch const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []; if ( types.length && + !(value === null && schema.nullable) && !types.some((type) => { if (type === "null") return value === null; if (type === "array") return Array.isArray(value); @@ -1094,7 +1095,7 @@ export function schemaExample( const selected = schemaExample(document, merged, depth + 1, name); if (schemaMatches(document, selected, schema)) return selected; } - return schemaExample(document, union[0], depth + 1, name); + return [null, false, 0, {}, []].find((candidate) => schemaMatches(document, candidate, schema)) ?? null; } if (schema.allOf?.length) { for (const [index, item] of schema.allOf.entries()) { @@ -1102,11 +1103,15 @@ export function schemaExample( const alternatives = nested.oneOf ?? nested.anyOf; if (!alternatives?.length) continue; for (const alternative of alternatives) { + const required = new Set([...(nested.required ?? []), ...(alternative.required ?? [])]); + const properties = { ...nested.properties, ...alternative.properties }; const replacement = { ...nested, + ...alternative, anyOf: undefined, oneOf: undefined, - allOf: [...(nested.allOf ?? []), alternative], + properties: Object.fromEntries(Object.entries(properties).filter(([property]) => required.has(property))), + required: [...required], }; const candidate = schemaExample( document, From e4673c93511c349bd4e50a64ea5e3d525227f838 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:42:29 +0200 Subject: [PATCH 29/80] Honor narrow ranges and finite keys --- lib/generators/python.test.ts | 9 +++++++++ lib/openapi.ts | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index c305aec..038fd3b 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -599,6 +599,15 @@ describe("Python generator", () => { ], }), ).toEqual({ error: "example-error", failureId: "resource-id" }); + expect(schemaExample(document, { exclusiveMaximum: 0.6, exclusiveMinimum: 0.5, type: "number" })).toBe(0.55); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { enum: ["a", "b"], type: "string" }, + type: "object", + }), + ).toEqual({ a: "example-a", b: "example-b" }); expect(schemaExample(document, { enum: ["a", "b"], pattern: "^b$", type: "string" })).toBe("b"); expect( schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), diff --git a/lib/openapi.ts b/lib/openapi.ts index bdb14c8..10db604 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1183,6 +1183,9 @@ export function schemaExample( const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; const round = (candidate: number) => Number(candidate.toPrecision(15)); + if (type === "number" && !multiple && minimum?.exclusive && maximum?.exclusive && !scalarMatches(1, schema)) { + return round((minimum.value + maximum.value) / 2); + } let value = 1; if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { value = minimum.value; @@ -1238,10 +1241,14 @@ export function schemaExample( ? "key" : String(schemaExample(document, schema.propertyNames, depth + 1, "key")); for (let index = 1; values.size < target; index += 1) { + const alternatives = (schema.propertyNames?.enum ?? []).filter( + (candidate): candidate is string => typeof candidate === "string" && !values.has(candidate), + ); const candidates = index === 1 - ? [key] + ? [key, ...alternatives] : [ + ...alternatives, `${key}${index}`, `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`, From 0b4a84d0d77808b8459e266b787a771d143838ec Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:46:00 +0200 Subject: [PATCH 30/80] Preserve writable request minimums --- lib/generators/python.test.ts | 7 +++++++ lib/openapi.ts | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 038fd3b..ebc055f 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -330,6 +330,13 @@ describe("Python generator", () => { type: "object", }; expect(requestBodyExample(minimalDocument, create)).toBe("{}"); + createMedia[1].schema = { + additionalProperties: { type: "string" }, + minProperties: 1, + properties: { id: { readOnly: true, type: "string" } }, + type: "object", + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "key": "example-key"\n}'); createMedia[1].schema = { allOf: [{ properties: { a: { type: "integer" }, b: { type: "integer" } }, type: "object" }, { required: ["b"] }], }; diff --git a/lib/openapi.ts b/lib/openapi.ts index 10db604..e46f9aa 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1482,7 +1482,7 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper const selected = properties.some(([name]) => required.has(name)) ? properties.filter(([name]) => required.has(name)) : properties.slice(0, 1); - const minimum = Math.min(object?.minProperties ?? 0, properties.length); + const minimum = object?.minProperties ?? 0; if (selected.length < minimum) { const names = new Set(selected.map(([name]) => name)); selected.push(...properties.filter(([name]) => !names.has(name)).slice(0, minimum - selected.length)); @@ -1507,6 +1507,20 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper selected.push([name, {}]); } } + if (selected.length < minimum) { + const writable = schemaExample(document, { + ...object, + properties: Object.fromEntries(properties), + required: [...required].filter((name) => properties.some(([property]) => property === name)), + }); + if (writable && typeof writable === "object" && !Array.isArray(writable)) { + Object.assign(values, writable); + for (const name of Object.keys(writable)) { + if (selected.length >= minimum) break; + if (!selectedNames.has(name)) selected.push([name, {}]); + } + } + } if (object?.maxProperties !== undefined) selected.splice(object.maxProperties); if (selected.length) example = Object.fromEntries(selected.map(([name]) => [name, values[name]])); else if (named.length) example = {}; From 5b577932cc64bfb987c57d1babbd96795edd554e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:50:02 +0200 Subject: [PATCH 31/80] Handle object-only union branches --- lib/generators/python.test.ts | 12 ++++++++++ lib/openapi.ts | 42 ++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index ebc055f..705c8c4 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -615,6 +615,18 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ a: "example-a", b: "example-b" }); + expect( + schemaExample(document, { + allOf: [{ format: "custom", type: "string" }, { pattern: "^abc$" }], + }), + ).toBe("abc"); + expect( + schemaExample(document, { + oneOf: [{ required: ["a"] }, { required: ["b"] }], + properties: { a: { type: "integer" }, b: { type: "integer" } }, + type: "object", + }), + ).toEqual({ a: 1 }); expect(schemaExample(document, { enum: ["a", "b"], pattern: "^b$", type: "string" })).toBe("b"); expect( schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), diff --git a/lib/openapi.ts b/lib/openapi.ts index e46f9aa..4feec52 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -781,11 +781,28 @@ function stringFormatMatches(value: string, format?: string): boolean { ); if (format === "uuid") return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); - return !format || format === "binary" || format === "byte" || format === "password"; + return true; } function stringExample(schema: JsonSchema, name?: string, patterns = schema.pattern ? [schema.pattern] : []): string { const key = name?.toLowerCase() ?? ""; + const knownFormats = new Set([ + "binary", + "byte", + "date", + "date-time", + "duration", + "email", + "hostname", + "ipv4", + "ipv6", + "password", + "time", + "uri", + "url", + "uuid", + ]); + if (schema.format && !knownFormats.has(schema.format) && !patterns.length) return `<${schema.format} value>`; let value = name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; if (schema.format === "date") value = "2026-01-01"; else if (schema.format === "date-time") value = "2026-01-01T00:00:00Z"; @@ -1085,9 +1102,28 @@ export function schemaExample( if (schemaMatches(document, null, schema)) return null; continue; } - if (type === "object" || variant.properties) { + if ( + type === "object" || + variant.properties || + variant.required || + variant.additionalProperties !== undefined || + variant.minProperties !== undefined || + variant.maxProperties !== undefined || + variant.propertyNames + ) { const combined = objectSchema(document, { allOf: [siblings, variant] }); - const selected = schemaExample(document, combined ?? variant, depth + 1, name); + const required = new Set([...(siblings.required ?? []), ...(variant.required ?? [])]); + const narrowed = + combined && variant.required + ? { + ...combined, + allOf: undefined, + properties: Object.fromEntries( + Object.entries(combined.properties ?? {}).filter(([property]) => required.has(property)), + ), + } + : combined; + const selected = schemaExample(document, narrowed ?? variant, depth + 1, name); if (schemaMatches(document, selected, schema)) return selected; continue; } From 782d11814f4dfb7ac617fa7a849520882ccd9823 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:53:47 +0200 Subject: [PATCH 32/80] Constrain generated object property names --- lib/generators/python.test.ts | 30 ++++++++++++++++++++++++++++++ lib/openapi.ts | 31 +++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 705c8c4..0eabb3d 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -627,6 +627,36 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ a: 1 }); + expect(schemaExample(document, { minLength: 3, pattern: "^[0-9]+$", type: "string" })).toBe("000"); + expect( + schemaExample(document, { + additionalProperties: false, + properties: { a: { type: "integer" }, b: { type: "integer" } }, + propertyNames: { pattern: "^a$", type: "string" }, + type: "object", + }), + ).toEqual({ a: 1 }); + expect( + schemaExample(document, { + anyOf: [{ additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }], + minProperties: 1, + properties: { a: { type: "integer" }, b: { type: "integer" } }, + type: "object", + }), + ).toEqual({ a: 1 }); + expect( + schemaExample(document, { + allOf: [ + { + properties: { a: { type: "integer" }, b: { type: "integer" } }, + propertyNames: { pattern: "^[a-z]+$", type: "string" }, + required: ["a"], + type: "object", + }, + { propertyNames: { pattern: "^a$", type: "string" } }, + ], + }), + ).toEqual({ a: 1 }); expect(schemaExample(document, { enum: ["a", "b"], pattern: "^b$", type: "string" })).toBe("b"); expect( schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), diff --git a/lib/openapi.ts b/lib/openapi.ts index 4feec52..3d15c9c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -849,8 +849,12 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt if (grouped) return grouped.split("|"); const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)\})/g)] : []; - if (sequence?.includes("{") || ranges.length > 1) { - return [ranges.map((match) => match[1]?.repeat(match[3] ? Number(match[3]) : 1)).join("")]; + if (sequence?.includes("{") || ranges.length > 1 || (sequence?.endsWith("+") && schema.minLength)) { + return [ + ranges + .map((match) => match[1]?.repeat(match[3] ? Number(match[3]) : Math.max(1, schema.minLength ?? 1))) + .join(""), + ]; } return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); @@ -1113,13 +1117,20 @@ export function schemaExample( ) { const combined = objectSchema(document, { allOf: [siblings, variant] }); const required = new Set([...(siblings.required ?? []), ...(variant.required ?? [])]); + const narrow = variant.required || variant.additionalProperties === false || variant.propertyNames; const narrowed = - combined && variant.required + combined && narrow ? { ...combined, allOf: undefined, properties: Object.fromEntries( - Object.entries(combined.properties ?? {}).filter(([property]) => required.has(property)), + Object.entries(combined.properties ?? {}).filter( + ([property]) => + required.has(property) || + (!variant.required && + (variant.additionalProperties !== false || Object.hasOwn(variant.properties ?? {}, property)) && + (!variant.propertyNames || schemaMatches(document, property, variant.propertyNames))), + ), ), } : combined; @@ -1174,9 +1185,15 @@ export function schemaExample( const branch = objectSchema(document, item); return branch?.additionalProperties === false ? [new Set(Object.keys(branch.properties ?? {}))] : []; }); + const propertyNames = schema.allOf.flatMap((item) => { + const branch = objectSchema(document, item); + return branch?.propertyNames ? [branch.propertyNames] : []; + }); const common = Object.fromEntries( Object.entries(selected as Record).filter( - ([property]) => required.has(property) || closed.every((properties) => properties.has(property)), + ([property]) => + (required.has(property) || closed.every((properties) => properties.has(property))) && + propertyNames.every((constraint) => schemaMatches(document, property, constraint)), ), ); if (schemaMatches(document, common, schema)) return common; @@ -1246,7 +1263,9 @@ export function schemaExample( return value; } if (type === "object" || schema.properties) { - const properties = Object.entries(schema.properties ?? {}); + const properties = Object.entries(schema.properties ?? {}).filter( + ([property]) => !schema.propertyNames || schemaMatches(document, property, schema.propertyNames), + ); const required = new Set(schema.required ?? []); const selected = schema.maxProperties === undefined From 7d050ee3bcff9259cffe791ba3ef18d3b7ecd0f5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 04:58:17 +0200 Subject: [PATCH 33/80] Preserve valid composed request fields --- lib/generators/python.test.ts | 8 ++++++++ lib/openapi.ts | 16 +++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 0eabb3d..ec4d782 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -341,6 +341,13 @@ describe("Python generator", () => { allOf: [{ properties: { a: { type: "integer" }, b: { type: "integer" } }, type: "object" }, { required: ["b"] }], }; expect(requestBodyExample(minimalDocument, create)).toBe('{\n "b": 1\n}'); + createMedia[1].schema = { + allOf: [ + { properties: { a: { type: "integer" }, b: { type: "integer" } }, type: "object" }, + { propertyNames: { pattern: "^b$", type: "string" } }, + ], + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "b": 1\n}'); expect(sdkArguments(minimalDocument, unionCreate)).toMatchObject([ { description: "Upload a file Or Use a source URL", @@ -788,6 +795,7 @@ describe("Python generator", () => { }), ).toBe(1.2); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); + expect(schemaExample(document, { maximum: 1.9, minimum: 1.5, type: ["integer", "number"] })).toBe(1.5); expect( schemaExample(document, { exclusiveMaximum: 0.25, diff --git a/lib/openapi.ts b/lib/openapi.ts index 3d15c9c..6d30066 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1214,7 +1214,12 @@ export function schemaExample( return null; } - const type = Array.isArray(schema.type) ? schema.type.find((value) => value !== "null") : schema.type; + const type = Array.isArray(schema.type) + ? schema.type.includes("number") && + schema.type.every((value) => value === "integer" || value === "number" || value === "null") + ? "number" + : schema.type.find((value) => value !== "null") + : schema.type; if (schema.type === "null" || (Array.isArray(schema.type) && schema.type.every((value) => value === "null"))) return null; if (type === "array") { @@ -1534,15 +1539,16 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper const named = Object.entries(object?.properties ?? {}); const properties = named.filter(([, property]) => !resolveSchema(document, property)?.readOnly); const required = new Set(object?.required ?? []); - const selected = properties.some(([name]) => required.has(name)) - ? properties.filter(([name]) => required.has(name)) - : properties.slice(0, 1); + const values = example as Record; + const present = properties.filter(([name]) => Object.hasOwn(values, name)); + const selected = present.some(([name]) => required.has(name)) + ? present.filter(([name]) => required.has(name)) + : present.slice(0, 1); const minimum = object?.minProperties ?? 0; if (selected.length < minimum) { const names = new Set(selected.map(([name]) => name)); selected.push(...properties.filter(([name]) => !names.has(name)).slice(0, minimum - selected.length)); } - const values = example as Record; const selectedNames = new Set(selected.map(([name]) => name)); for (const name of required) { const property = object?.properties?.[name]; From e3276d2a611034b21fdb922d2ed5949fa4868649 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:02:42 +0200 Subject: [PATCH 34/80] Generate unbounded range examples --- lib/generators/python.test.ts | 1 + lib/openapi.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index ec4d782..8c1e63e 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -635,6 +635,7 @@ describe("Python generator", () => { }), ).toEqual({ a: 1 }); expect(schemaExample(document, { minLength: 3, pattern: "^[0-9]+$", type: "string" })).toBe("000"); + expect(schemaExample(document, { pattern: "^[0-9]+$", type: "string" })).toBe("0"); expect( schemaExample(document, { additionalProperties: false, diff --git a/lib/openapi.ts b/lib/openapi.ts index 6d30066..6fb015c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -844,12 +844,15 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const expressions = patterns.map((pattern) => new RegExp(pattern)); const candidates = [ value, + ...(key === "model" ? ["yolo26n", "yolo26"] : []), + ...(patterns.some((pattern) => pattern.includes("[A-Z]+")) ? ["KEY"] : []), + "example", ...patterns.flatMap((pattern) => { const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)\})/g)] : []; - if (sequence?.includes("{") || ranges.length > 1 || (sequence?.endsWith("+") && schema.minLength)) { + if (sequence?.includes("{") || ranges.length > 1 || sequence?.endsWith("+")) { return [ ranges .map((match) => match[1]?.repeat(match[3] ? Number(match[3]) : Math.max(1, schema.minLength ?? 1))) @@ -863,11 +866,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt }), ...(schema.format === "email" ? ["a@b.co"] : []), ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), - ...(key === "model" ? ["yolo26n", "yolo26"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), - ...(patterns.some((pattern) => pattern.includes("[A-Z]+")) ? ["KEY"] : []), - "example", ].map(constrainLength); return ( candidates.find( From ae7a20bff9dfd4e67d97625e9c11fade075bc13f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:07:53 +0200 Subject: [PATCH 35/80] Fill constrained composed objects --- lib/generators/python.test.ts | 32 ++++++++++++++++++++++++++++++++ lib/openapi.ts | 31 ++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 8c1e63e..dc73620 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -337,6 +337,14 @@ describe("Python generator", () => { type: "object", }; expect(requestBodyExample(minimalDocument, create)).toBe('{\n "key": "example-key"\n}'); + createMedia[1].schema = { + additionalProperties: { type: "string" }, + minProperties: 2, + properties: { a: { type: "integer" } }, + propertyNames: { pattern: "^key[0-9]*$", type: "string" }, + type: "object", + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "key": "example-key",\n "key2": "example-key2"\n}'); createMedia[1].schema = { allOf: [{ properties: { a: { type: "integer" }, b: { type: "integer" } }, type: "object" }, { required: ["b"] }], }; @@ -636,6 +644,7 @@ describe("Python generator", () => { ).toEqual({ a: 1 }); expect(schemaExample(document, { minLength: 3, pattern: "^[0-9]+$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^[0-9]+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { pattern: "^b[0-9]*$", type: "string" })).toBe("b0"); expect( schemaExample(document, { additionalProperties: false, @@ -665,6 +674,29 @@ describe("Python generator", () => { ], }), ).toEqual({ a: 1 }); + expect( + schemaExample(document, { + allOf: [ + { + oneOf: [{ additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }], + }, + { minProperties: 1, type: "object" }, + ], + }), + ).toEqual({ a: 1 }); + expect( + schemaExample(document, { + allOf: [ + { + additionalProperties: { type: "string" }, + minProperties: 2, + properties: { b: { type: "integer" } }, + type: "object", + }, + { propertyNames: { pattern: "^b[0-9]*$", type: "string" } }, + ], + }), + ).toEqual({ b: 1, b0: "example-b0" }); expect(schemaExample(document, { enum: ["a", "b"], pattern: "^b$", type: "string" })).toBe("b"); expect( schemaExample(document, { maxProperties: 0, properties: { id: { type: "integer" } }, type: "object" }), diff --git a/lib/openapi.ts b/lib/openapi.ts index 6fb015c..a30d5b8 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -848,6 +848,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(patterns.some((pattern) => pattern.includes("[A-Z]+")) ? ["KEY"] : []), "example", ...patterns.flatMap((pattern) => { + const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); + if (suffixed?.[1] && suffixed[2]) { + return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; + } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; @@ -1157,7 +1161,11 @@ export function schemaExample( ...alternative, anyOf: undefined, oneOf: undefined, - properties: Object.fromEntries(Object.entries(properties).filter(([property]) => required.has(property))), + properties: Object.fromEntries( + Object.entries(properties).filter( + ([property]) => required.has(property) || Object.hasOwn(alternative.properties ?? {}, property), + ), + ), required: [...required], }; const candidate = schemaExample( @@ -1174,7 +1182,20 @@ export function schemaExample( } const object = objectSchema(document, schema); if (object?.properties) { - const selected = schemaExample(document, { ...object, allOf: undefined }, depth + 1, name); + const names = schema.allOf.flatMap((item) => { + const branch = resolveSchema(document, item) ?? item; + return branch.propertyNames ? [branch.propertyNames] : []; + }); + const selected = schemaExample( + document, + { + ...object, + allOf: undefined, + ...(names.length ? { propertyNames: names.length === 1 ? names[0] : { allOf: names } } : {}), + }, + depth + 1, + name, + ); if (schemaMatches(document, selected, schema)) return selected; for (const item of schema.allOf) { const candidate = schemaExample(document, item, depth + 1, name); @@ -1547,7 +1568,11 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper const minimum = object?.minProperties ?? 0; if (selected.length < minimum) { const names = new Set(selected.map(([name]) => name)); - selected.push(...properties.filter(([name]) => !names.has(name)).slice(0, minimum - selected.length)); + selected.push( + ...properties + .filter(([name]) => Object.hasOwn(values, name) && !names.has(name)) + .slice(0, minimum - selected.length), + ); } const selectedNames = new Set(selected.map(([name]) => name)); for (const name of required) { From bc8bc66d492a13501c450f11c8c9ddba95c501cd Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:12:24 +0200 Subject: [PATCH 36/80] Cover simple patterns and integer multiples --- lib/generators/python.test.ts | 5 +++++ lib/openapi.ts | 26 ++++++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index dc73620..2137280 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -644,6 +644,10 @@ describe("Python generator", () => { ).toEqual({ a: 1 }); expect(schemaExample(document, { minLength: 3, pattern: "^[0-9]+$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^[0-9]+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { pattern: "^\\d+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); + expect(schemaExample(document, { type: "string" }, 0, "apiKey")).toBe("your-api-key"); + expect(schemaExample(document, { type: "string" }, 0, "baseModel")).toBe("yolo26n.pt"); expect(schemaExample(document, { pattern: "^b[0-9]*$", type: "string" })).toBe("b0"); expect( schemaExample(document, { @@ -829,6 +833,7 @@ describe("Python generator", () => { ).toBe(1.2); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect(schemaExample(document, { maximum: 1.9, minimum: 1.5, type: ["integer", "number"] })).toBe(1.5); + expect(schemaExample(document, { multipleOf: 0.3, type: "integer" })).toBe(3); expect( schemaExample(document, { exclusiveMaximum: 0.25, diff --git a/lib/openapi.ts b/lib/openapi.ts index a30d5b8..3e3d833 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -785,7 +785,7 @@ function stringFormatMatches(value: string, format?: string): boolean { } function stringExample(schema: JsonSchema, name?: string, patterns = schema.pattern ? [schema.pattern] : []): string { - const key = name?.toLowerCase() ?? ""; + const key = name?.replace(/[-_]/g, "").toLowerCase() ?? ""; const knownFormats = new Set([ "binary", "byte", @@ -821,8 +821,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt else if (key === "region") value = "us-east-1"; else if (key === "model" || key === "basemodel") value = "yolo26n.pt"; else if (key === "data") value = "ul://jane-doe/datasets/coco8"; - else if (key === "client_email") value = "jane@example.com"; - else if (key.includes("apikey") || key.includes("api_key")) value = "your-api-key"; + else if (key === "clientemail") value = "jane@example.com"; + else if (key.includes("apikey")) value = "your-api-key"; else if (key === "owner" || key === "username") value = "jane-doe"; else if (key === "project" || key.endsWith("projectslug")) value = "example-project"; else if (key === "dataset") value = "coco8"; @@ -848,6 +848,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(patterns.some((pattern) => pattern.includes("[A-Z]+")) ? ["KEY"] : []), "example", ...patterns.flatMap((pattern) => { + if (pattern === "^$") return [""]; + if (pattern === "^\\d+$") return ["0".repeat(Math.max(1, schema.minLength ?? 1))]; const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); if (suffixed?.[1] && suffixed[2]) { return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; @@ -1250,7 +1252,15 @@ export function schemaExample( if (type === "boolean") return false; if (type === "integer" || type === "number") { const multiple = schema.multipleOf && schema.multipleOf > 0 ? schema.multipleOf : undefined; - const step = multiple ?? 1; + const alignment = (() => { + if (!multiple || type !== "integer") return multiple; + const [coefficient, exponent = "0"] = String(multiple).toLowerCase().split("e"); + const scale = 10 ** Math.max(0, (coefficient.split(".")[1] ?? "").length - Number(exponent)); + const numerator = Math.round(multiple * scale); + const gcd = (a: number, b: number): number => (b ? gcd(b, a % b) : a); + return numerator / gcd(numerator, scale); + })(); + const step = alignment ?? 1; const minimums = [ ...(schema.minimum === undefined ? [] : [{ exclusive: schema.exclusiveMinimum === true, value: schema.minimum }]), ...(typeof schema.exclusiveMinimum === "number" ? [{ exclusive: true, value: schema.exclusiveMinimum }] : []), @@ -1269,21 +1279,21 @@ export function schemaExample( if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { value = minimum.value; if (type === "integer") value = Math.ceil(value); - if (multiple) value = round(Math.ceil(value / multiple) * multiple); + if (alignment) value = round(Math.ceil(value / alignment) * alignment); if (minimum.exclusive && value <= minimum.value) value = round(value + step); } else { if (type === "integer") value = Math.ceil(value); - if (multiple) value = round(Math.ceil(value / multiple) * multiple); + if (alignment) value = round(Math.ceil(value / alignment) * alignment); } if (maximum && (value > maximum.value || (maximum.exclusive && value >= maximum.value))) { value = maximum.value - (maximum.exclusive ? step : 0); if (type === "integer") value = maximum.exclusive ? Math.ceil(maximum.value) - 1 : Math.floor(maximum.value); - if (multiple) value = round(Math.floor(value / multiple) * multiple); + if (alignment) value = round(Math.floor(value / alignment) * alignment); } if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { value = minimum.value; if (type === "integer") value = Math.ceil(value); - if (multiple) value = round(Math.ceil(value / multiple) * multiple); + if (alignment) value = round(Math.ceil(value / alignment) * alignment); if (minimum.exclusive && value <= minimum.value) value = round(value + step); } return value; From a6cf9e6a3ac944fe16eeb4cd481219ce264d65db Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:16:53 +0200 Subject: [PATCH 37/80] Select valid heterogeneous examples --- lib/generators/python.test.ts | 9 +++++++++ lib/openapi.ts | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2137280..ca7ece2 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -645,6 +645,7 @@ describe("Python generator", () => { expect(schemaExample(document, { minLength: 3, pattern: "^[0-9]+$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^[0-9]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^\\d+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { pattern: "^\\d{3}$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); expect(schemaExample(document, { type: "string" }, 0, "apiKey")).toBe("your-api-key"); expect(schemaExample(document, { type: "string" }, 0, "baseModel")).toBe("yolo26n.pt"); @@ -834,6 +835,14 @@ describe("Python generator", () => { expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); expect(schemaExample(document, { maximum: 1.9, minimum: 1.5, type: ["integer", "number"] })).toBe(1.5); expect(schemaExample(document, { multipleOf: 0.3, type: "integer" })).toBe(3); + expect( + schemaExample(document, { + maxItems: 0, + minItems: 1, + pattern: "^a$", + type: ["array", "string"], + }), + ).toBe("a"); expect( schemaExample(document, { exclusiveMaximum: 0.25, diff --git a/lib/openapi.ts b/lib/openapi.ts index 3e3d833..0d2cef6 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -849,7 +849,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt "example", ...patterns.flatMap((pattern) => { if (pattern === "^$") return [""]; - if (pattern === "^\\d+$") return ["0".repeat(Math.max(1, schema.minLength ?? 1))]; + const digits = pattern.match(/^\^\\d(?:\{(\d+)\}|\+)\$$/); + if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); if (suffixed?.[1] && suffixed[2]) { return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; @@ -1237,6 +1238,16 @@ export function schemaExample( return null; } + if (Array.isArray(schema.type)) { + const types = schema.type.filter((value) => value !== "null"); + if (schema.type.includes("null") || types.some((value) => value !== "integer" && value !== "number")) { + for (const candidateType of schema.type) { + const candidate = schemaExample(document, { ...schema, type: candidateType }, depth + 1, name); + if (schemaMatches(document, candidate, schema)) return candidate; + } + return null; + } + } const type = Array.isArray(schema.type) ? schema.type.includes("number") && schema.type.every((value) => value === "integer" || value === "number" || value === "null") From a10acbade9bb5051aa0b9ad5ce42788f531cdec1 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:22:11 +0200 Subject: [PATCH 38/80] Preserve composed property name constraints --- lib/generators/python.test.ts | 10 ++++++++++ lib/openapi.ts | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index ca7ece2..183f4c4 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -12,6 +12,7 @@ import { getAuthentication, getOperations, type OpenApiDocument, + objectSchema, requestBodyExample, requestMedia, resolveServerUrl, @@ -647,6 +648,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^\\d+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^\\d{3}$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); + expect(schemaExample(document, { pattern: "foo", type: "string" })).toBe("foo"); expect(schemaExample(document, { type: "string" }, 0, "apiKey")).toBe("your-api-key"); expect(schemaExample(document, { type: "string" }, 0, "baseModel")).toBe("yolo26n.pt"); expect(schemaExample(document, { pattern: "^b[0-9]*$", type: "string" })).toBe("b0"); @@ -679,6 +681,14 @@ describe("Python generator", () => { ], }), ).toEqual({ a: 1 }); + expect( + objectSchema(document, { + allOf: [ + { properties: { a: { type: "integer" }, b: { type: "integer" } }, type: "object" }, + { propertyNames: { pattern: "^a$", type: "string" } }, + ], + })?.properties, + ).toEqual({ a: { type: "integer" } }); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 0d2cef6..16800be 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -684,6 +684,8 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde const additional = composed.flatMap((item) => typeof item.additionalProperties === "object" ? [item.additionalProperties] : [], ); + const names = composed.flatMap((item) => (item.propertyNames ? [item.propertyNames] : [])); + const propertyNames = names.length === 1 ? names[0] : names.length ? { allOf: names } : undefined; return { ...schema, ...(composed.some((item) => item.additionalProperties === false) @@ -693,7 +695,12 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde : {}), ...(maximums.length ? { maxProperties: Math.min(...maximums) } : {}), ...(minimums.length ? { minProperties: Math.max(...minimums) } : {}), - properties: Object.assign({}, schema.properties, ...objects.map((item) => item?.properties)), + ...(propertyNames ? { propertyNames } : {}), + properties: Object.fromEntries( + Object.entries( + Object.assign({}, schema.properties, ...objects.map((item) => item?.properties)), + ).filter(([property]) => !propertyNames || schemaMatches(document, property, propertyNames)), + ), required: [...new Set([...(schema.required ?? []), ...objects.flatMap((item) => item?.required ?? [])])], type: "object", }; @@ -848,6 +855,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(patterns.some((pattern) => pattern.includes("[A-Z]+")) ? ["KEY"] : []), "example", ...patterns.flatMap((pattern) => { + if (/^[a-zA-Z0-9._-]+$/.test(pattern)) return [pattern]; if (pattern === "^$") return [""]; const digits = pattern.match(/^\^\\d(?:\{(\d+)\}|\+)\$$/); if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; From aeacb476fb31f957ea544cd357b1c5e6f0635057 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:25:45 +0200 Subject: [PATCH 39/80] Cover regex repetition ranges --- lib/generators/python.test.ts | 2 ++ lib/openapi.ts | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 183f4c4..a03568c 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -647,6 +647,8 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[0-9]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^\\d+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^\\d{3}$", type: "string" })).toBe("000"); + expect(schemaExample(document, { pattern: "^\\d{3,5}$", type: "string" })).toBe("000"); + expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); expect(schemaExample(document, { pattern: "foo", type: "string" })).toBe("foo"); expect(schemaExample(document, { type: "string" }, 0, "apiKey")).toBe("your-api-key"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 16800be..ff2e06c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -857,8 +857,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...patterns.flatMap((pattern) => { if (/^[a-zA-Z0-9._-]+$/.test(pattern)) return [pattern]; if (pattern === "^$") return [""]; - const digits = pattern.match(/^\^\\d(?:\{(\d+)\}|\+)\$$/); + const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,\d+)?\}|[+*])\$$/); if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; + const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,\d+)?\}|[+*])\$$/); + if (repeatedRange?.[1]) { + return [ + repeatedRange[1].repeat(repeatedRange[2] ? Number(repeatedRange[2]) : Math.max(1, schema.minLength ?? 1)), + ]; + } const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); if (suffixed?.[1] && suffixed[2]) { return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; From 249fb5e01f17759047bfac3660f9ec86f19c2399 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:30:09 +0200 Subject: [PATCH 40/80] Compose object property constraints --- lib/generators/python.test.ts | 19 +++++++++++++++++++ lib/openapi.ts | 22 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a03568c..4e167a5 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -651,6 +651,8 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); expect(schemaExample(document, { pattern: "foo", type: "string" })).toBe("foo"); + expect(schemaExample(document, { pattern: "foo|bar", type: "string" })).toBe("foo"); + expect(schemaExample(document, { pattern: "^foo", type: "string" })).toBe("foo"); expect(schemaExample(document, { type: "string" }, 0, "apiKey")).toBe("your-api-key"); expect(schemaExample(document, { type: "string" }, 0, "baseModel")).toBe("yolo26n.pt"); expect(schemaExample(document, { pattern: "^b[0-9]*$", type: "string" })).toBe("b0"); @@ -691,6 +693,23 @@ describe("Python generator", () => { ], })?.properties, ).toEqual({ a: { type: "integer" } }); + expect( + objectSchema(document, { + allOf: [ + { properties: { name: { pattern: "^a", type: "string" } } }, + { properties: { name: { minLength: 2, type: "string" } } }, + ], + })?.properties?.name, + ).toEqual({ + allOf: [ + { pattern: "^a", type: "string" }, + { minLength: 2, type: "string" }, + ], + }); + expect(schemaConstraints(document, { maxProperties: 3, minProperties: 1, type: "object" })).toEqual([ + "minimum properties 1", + "maximum properties 3", + ]); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index ff2e06c..3b208e1 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -686,6 +686,15 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde ); const names = composed.flatMap((item) => (item.propertyNames ? [item.propertyNames] : [])); const propertyNames = names.length === 1 ? names[0] : names.length ? { allOf: names } : undefined; + const properties: Record = {}; + for (const item of composed) { + for (const [name, property] of Object.entries(item.properties ?? {})) { + properties[name] = + properties[name] && !schemasEqual(properties[name], property) + ? { allOf: [properties[name], property] } + : property; + } + } return { ...schema, ...(composed.some((item) => item.additionalProperties === false) @@ -697,9 +706,9 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde ...(minimums.length ? { minProperties: Math.max(...minimums) } : {}), ...(propertyNames ? { propertyNames } : {}), properties: Object.fromEntries( - Object.entries( - Object.assign({}, schema.properties, ...objects.map((item) => item?.properties)), - ).filter(([property]) => !propertyNames || schemaMatches(document, property, propertyNames)), + Object.entries(properties).filter( + ([property]) => !propertyNames || schemaMatches(document, property, propertyNames), + ), ), required: [...new Set([...(schema.required ?? []), ...objects.flatMap((item) => item?.required ?? [])])], type: "object", @@ -856,6 +865,11 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt "example", ...patterns.flatMap((pattern) => { if (/^[a-zA-Z0-9._-]+$/.test(pattern)) return [pattern]; + const simple = pattern + .split("|") + .map((alternative) => alternative.replace(/^\^/, "").replace(/\$$/, "")) + .filter((alternative) => /^[a-zA-Z0-9._-]+$/.test(alternative)); + if (simple.length) return simple; if (pattern === "^$") return [""]; const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,\d+)?\}|[+*])\$$/); if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; @@ -1419,6 +1433,8 @@ export function schemaConstraints(document: OpenApiDocument, input: JsonSchema | if (schema.maxLength !== undefined) constraints.push(`maximum length ${schema.maxLength}`); if (schema.minItems !== undefined) constraints.push(`minimum items ${schema.minItems}`); if (schema.maxItems !== undefined) constraints.push(`maximum items ${schema.maxItems}`); + if (schema.minProperties !== undefined) constraints.push(`minimum properties ${schema.minProperties}`); + if (schema.maxProperties !== undefined) constraints.push(`maximum properties ${schema.maxProperties}`); if (schema.multipleOf !== undefined) constraints.push(`multiple of ${schema.multipleOf}`); if (schema.pattern) constraints.push(`pattern ${schema.pattern}`); if (schema.default !== undefined) constraints.push(`default ${String(schema.default)}`); From fc1cb9f7fdb4e33cb556ef6e8ad515caa7379185 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:34:19 +0200 Subject: [PATCH 41/80] Constrain fallback string examples --- lib/generators/python.test.ts | 2 ++ lib/openapi.ts | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 4e167a5..a42bc79 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -649,6 +649,8 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^\\d{3}$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^\\d{3,5}$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); + expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); + expect(schemaExample(document, { format: "custom", maxLength: 5, type: "string" })).toBe("`; + const constrainLength = (candidate: string) => + (schema.maxLength === undefined ? candidate : candidate.slice(0, schema.maxLength)).padEnd( + schema.minLength ?? 0, + "x", + ); + if (schema.format && !knownFormats.has(schema.format) && !patterns.length) { + return constrainLength(`<${schema.format} value>`); + } let value = name ? `example-${name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}` : "example"; if (schema.format === "date") value = "2026-01-01"; else if (schema.format === "date-time") value = "2026-01-01T00:00:00Z"; @@ -851,11 +858,6 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt else if (key.includes("message")) value = "Operation completed"; else if (key.includes("hash")) value = "a1b2c3d4"; else if (key.includes("color")) value = "#4f46e5"; - const constrainLength = (candidate: string) => - (schema.maxLength === undefined ? candidate : candidate.slice(0, schema.maxLength)).padEnd( - schema.minLength ?? 0, - "x", - ); try { const expressions = patterns.map((pattern) => new RegExp(pattern)); const candidates = [ @@ -879,6 +881,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt repeatedRange[1].repeat(repeatedRange[2] ? Number(repeatedRange[2]) : Math.max(1, schema.minLength ?? 1)), ]; } + const multiRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\]\{(\d+)\}\$$/); + if (multiRange?.[1] && multiRange[2]) return [multiRange[1].repeat(Number(multiRange[2]))]; const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); if (suffixed?.[1] && suffixed[2]) { return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; From f30d3748a44aaccba5b7122a08eebd26e7689021 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:37:59 +0200 Subject: [PATCH 42/80] Keep incompatible closed bodies intact --- lib/generators/python.test.ts | 16 ++++++++++++++++ lib/openapi.ts | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a42bc79..b9e788f 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -228,6 +228,13 @@ describe("Python generator", () => { }; const minimalBody = requestBodyExample(minimalDocument, create); expect(minimalBody).toBe('{\n "name": "Example name"\n}'); + createMedia[1].schema = { + allOf: [ + { additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }, + { additionalProperties: false, properties: { b: { type: "integer" } }, type: "object" }, + ], + }; + expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); const minimalCurl = curlCodeSample(minimalDocument, create, { body: minimalBody, environment: "EXAMPLE_API_KEY", @@ -650,7 +657,16 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^\\d{3,5}$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); + expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { format: "custom", maxLength: 5, type: "string" })).toBe(" objectSchema(document, variant)); + const closed = bodySchema?.allOf?.flatMap((item) => { + const branch = objectSchema(document, item); + return branch?.additionalProperties === false + ? [ + Object.keys(branch.properties ?? {}) + .sort() + .join("\0"), + ] + : []; + }); + const incompatibleClosedBody = Boolean(closed?.length && new Set(closed).size > 1); const exclusiveBody = variants?.length && variants.every((variant) => variant?.required?.length) && variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name))); - if (body?.properties && !exclusiveBody) { + if (body?.properties && !exclusiveBody && !incompatibleClosedBody) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; if (property.readOnly) continue; @@ -881,7 +892,9 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt repeatedRange[1].repeat(repeatedRange[2] ? Number(repeatedRange[2]) : Math.max(1, schema.minLength ?? 1)), ]; } - const multiRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\]\{(\d+)\}\$$/); + const multiRange = pattern.match( + /^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\]\{(\d+)(?:,\d*)?\}\$$/, + ); if (multiRange?.[1] && multiRange[2]) return [multiRange[1].repeat(Number(multiRange[2]))]; const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); if (suffixed?.[1] && suffixed[2]) { @@ -1387,6 +1400,7 @@ export function schemaExample( `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`, `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, + ...(/^[0-9]+$/.test(key) ? [String(Number(key) + index - 1).padStart(key.length, "0")] : []), ]; const property = candidates.find( (candidate) => !schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames), From c172f5a45119a2dd2a16f9902d2b029e1ffc99b3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:42:53 +0200 Subject: [PATCH 43/80] Respect branch-local closed objects --- lib/generators/python.test.ts | 23 +++++++++++++++++++++++ lib/openapi.ts | 15 +++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index b9e788f..dfb5f46 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -650,6 +650,20 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ a: 1 }); + expect( + schemaExample(document, { + oneOf: [ + { + additionalProperties: false, + minProperties: 1, + properties: { a: { type: "integer" } }, + required: [], + type: "object", + }, + ], + type: "object", + }), + ).toEqual({ a: 1 }); expect(schemaExample(document, { minLength: 3, pattern: "^[0-9]+$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^[0-9]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^\\d+$", type: "string" })).toBe("0"); @@ -658,6 +672,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); + expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { format: "custom", maxLength: 5, type: "string" })).toBe(" { ], })?.properties, ).toEqual({ a: { type: "integer" } }); + expect( + objectSchema(document, { + allOf: [ + { additionalProperties: false, properties: { a: { type: "integer" } } }, + { properties: { b: { type: "integer" } } }, + ], + })?.properties, + ).toEqual({ a: { type: "integer" } }); expect( objectSchema(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 5b61639..8d0de2b 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -706,6 +706,12 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde : property; } } + const closed = objects.filter((item) => item.additionalProperties === false); + if (closed.length) { + for (const property of Object.keys(properties)) { + if (closed.some((item) => !Object.hasOwn(item.properties ?? {}, property))) delete properties[property]; + } + } return { ...schema, ...(composed.some((item) => item.additionalProperties === false) @@ -884,9 +890,9 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt .filter((alternative) => /^[a-zA-Z0-9._-]+$/.test(alternative)); if (simple.length) return simple; if (pattern === "^$") return [""]; - const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,\d+)?\}|[+*])\$$/); + const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,\d*)?\}|[+*])\$$/); if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; - const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,\d+)?\}|[+*])\$$/); + const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,\d*)?\}|[+*])\$$/); if (repeatedRange?.[1]) { return [ repeatedRange[1].repeat(repeatedRange[2] ? Number(repeatedRange[2]) : Math.max(1, schema.minLength ?? 1)), @@ -1169,7 +1175,8 @@ export function schemaExample( ) { const combined = objectSchema(document, { allOf: [siblings, variant] }); const required = new Set([...(siblings.required ?? []), ...(variant.required ?? [])]); - const narrow = variant.required || variant.additionalProperties === false || variant.propertyNames; + const hasRequired = Boolean(variant.required?.length); + const narrow = hasRequired || variant.additionalProperties === false || variant.propertyNames; const narrowed = combined && narrow ? { @@ -1179,7 +1186,7 @@ export function schemaExample( Object.entries(combined.properties ?? {}).filter( ([property]) => required.has(property) || - (!variant.required && + (!hasRequired && (variant.additionalProperties !== false || Object.hasOwn(variant.properties ?? {}, property)) && (!variant.propertyNames || schemaMatches(document, property, variant.propertyNames))), ), From ee7ae8992949e2a99aaad8a8f099112a14fa01f5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:46:49 +0200 Subject: [PATCH 44/80] Complete bounded pattern synthesis --- lib/generators/python.test.ts | 11 +++++++++++ lib/openapi.ts | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index dfb5f46..68c9759 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -673,6 +673,9 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); + expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); + expect(schemaExample(document, { pattern: "^[0-9A-F]+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { format: "ipv4", maxLength: 7, type: "string" })).toBe("1.1.1.1"); expect(schemaExample(document, { format: "custom", maxLength: 5, type: "string" })).toBe(" { type: "object", }), ).toEqual({ "000": "example-000", "001": "example-001" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^[a-z]{3}$", type: "string" }, + type: "object", + }), + ).toEqual({ key: "example-key", kez: "example-kez" }); expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); expect(schemaExample(document, { pattern: "foo", type: "string" })).toBe("foo"); expect(schemaExample(document, { pattern: "foo|bar", type: "string" })).toBe("foo"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 8d0de2b..e5532e0 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -892,16 +892,18 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt if (pattern === "^$") return [""]; const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,\d*)?\}|[+*])\$$/); if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; - const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,\d*)?\}|[+*])\$$/); + const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedRange?.[1]) { - return [ - repeatedRange[1].repeat(repeatedRange[2] ? Number(repeatedRange[2]) : Math.max(1, schema.minLength ?? 1)), - ]; + const count = Math.min( + Math.max(Number(repeatedRange[2] ?? 1), schema.minLength ?? 1), + repeatedRange[3] ? Number(repeatedRange[3]) : Number.POSITIVE_INFINITY, + ); + return [repeatedRange[1].repeat(count)]; } const multiRange = pattern.match( - /^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\]\{(\d+)(?:,\d*)?\}\$$/, + /^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\](?:\{(\d+)(?:,\d*)?\}|[+*])\$$/, ); - if (multiRange?.[1] && multiRange[2]) return [multiRange[1].repeat(Number(multiRange[2]))]; + if (multiRange?.[1]) return [multiRange[1].repeat(Math.max(Number(multiRange[2] ?? 1), schema.minLength ?? 1))]; const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); if (suffixed?.[1] && suffixed[2]) { return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; @@ -924,6 +926,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt }), ...(schema.format === "email" ? ["a@b.co"] : []), ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), + ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), ].map(constrainLength); @@ -1408,6 +1411,9 @@ export function schemaExample( `${key}${"x".repeat(index - 1)}`, `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, ...(/^[0-9]+$/.test(key) ? [String(Number(key) + index - 1).padStart(key.length, "0")] : []), + ...(/^[a-z]+$/.test(key) + ? [`${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 96) % 26))}`] + : []), ]; const property = candidates.find( (candidate) => !schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames), From 6a4665ad92c1170675757d6c78e635fe8f04e02a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:50:28 +0200 Subject: [PATCH 45/80] Fill repeated constrained examples --- lib/generators/python.test.ts | 11 ++++++++++- lib/openapi.ts | 14 +++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 68c9759..65e20ef 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -669,6 +669,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^\\d+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^\\d{3}$", type: "string" })).toBe("000"); expect(schemaExample(document, { pattern: "^\\d{3,5}$", type: "string" })).toBe("000"); + expect(schemaExample(document, { minLength: 4, pattern: "^\\d{3,5}$", type: "string" })).toBe("0000"); expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); @@ -692,7 +693,15 @@ describe("Python generator", () => { propertyNames: { pattern: "^[a-z]{3}$", type: "string" }, type: "object", }), - ).toEqual({ key: "example-key", kez: "example-kez" }); + ).toEqual({ key: "example-key", kea: "example-kea" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 3, + propertyNames: { pattern: "^[a-z]{2}$", type: "string" }, + type: "object", + }), + ).toEqual({ aa: "example-aa", ac: "example-ac", ad: "example-ad" }); expect(schemaExample(document, { pattern: "^$", type: "string" })).toBe(""); expect(schemaExample(document, { pattern: "foo", type: "string" })).toBe("foo"); expect(schemaExample(document, { pattern: "foo|bar", type: "string" })).toBe("foo"); diff --git a/lib/openapi.ts b/lib/openapi.ts index e5532e0..f370402 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -890,8 +890,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt .filter((alternative) => /^[a-zA-Z0-9._-]+$/.test(alternative)); if (simple.length) return simple; if (pattern === "^$") return [""]; - const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,\d*)?\}|[+*])\$$/); - if (digits) return ["0".repeat(digits[1] ? Number(digits[1]) : Math.max(1, schema.minLength ?? 1))]; + const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); + if (digits) { + const count = Math.min( + Math.max(Number(digits[1] ?? 1), schema.minLength ?? 1), + digits[2] ? Number(digits[2]) : Number.POSITIVE_INFINITY, + ); + return ["0".repeat(count)]; + } const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedRange?.[1]) { const count = Math.min( @@ -1412,7 +1418,9 @@ export function schemaExample( `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, ...(/^[0-9]+$/.test(key) ? [String(Number(key) + index - 1).padStart(key.length, "0")] : []), ...(/^[a-z]+$/.test(key) - ? [`${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 96) % 26))}`] + ? [ + `${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 97 + index) % 26))}`, + ] : []), ]; const property = candidates.find( From 1e9bf144fe7fdc3d5c037399ec7cfbc984c2b0f4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:54:46 +0200 Subject: [PATCH 46/80] Fix composed object and pattern examples --- lib/generators/python.test.ts | 15 +++++++++++++++ lib/openapi.ts | 9 +++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 65e20ef..2842992 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -686,6 +686,14 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ "000": "example-000", "001": "example-001" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^(ab|cd)$", type: "string" }, + type: "object", + }), + ).toEqual({ ab: "example-ab", cd: "example-cd" }); expect( schemaExample(document, { additionalProperties: { type: "string" }, @@ -746,6 +754,13 @@ describe("Python generator", () => { ], })?.properties, ).toEqual({ a: { type: "integer" } }); + expect( + objectSchema(document, { + additionalProperties: false, + allOf: [{ properties: { a: { type: "integer" } }, type: "object" }], + type: "object", + })?.properties, + ).toEqual({}); expect( objectSchema(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index f370402..24c7435 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -706,7 +706,7 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde : property; } } - const closed = objects.filter((item) => item.additionalProperties === false); + const closed = composed.filter((item) => item.additionalProperties === false); if (closed.length) { for (const property of Object.keys(properties)) { if (closed.some((item) => !Object.hasOwn(item.properties ?? {}, property))) delete properties[property]; @@ -1407,11 +1407,16 @@ export function schemaExample( const alternatives = (schema.propertyNames?.enum ?? []).filter( (candidate): candidate is string => typeof candidate === "string" && !values.has(candidate), ); + const patternAlternatives = schema.propertyNames?.pattern + ?.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1] + ?.split("|") + .filter((candidate) => !values.has(candidate)); const candidates = index === 1 - ? [key, ...alternatives] + ? [key, ...alternatives, ...(patternAlternatives ?? [])] : [ ...alternatives, + ...(patternAlternatives ?? []), `${key}${index}`, `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`, From c48970ed3779e3c8ce67dca8b9ccbeec0f70b398 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 05:59:51 +0200 Subject: [PATCH 47/80] Preserve exclusive request bodies --- lib/generators/python.test.ts | 11 +++++++++++ lib/openapi.ts | 9 ++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2842992..d024ca7 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -376,6 +376,17 @@ describe("Python generator", () => { const undescribedVariants = requestMedia(undescribedUnion)?.[1].schema?.oneOf; for (const variant of undescribedVariants ?? []) delete variant.description; expect(sdkArguments(minimalDocument, undescribedUnion)).toMatchObject([{ description: "Request body." }]); + const optionalClosedUnion = structuredClone(unionCreate); + const optionalClosedMedia = requestMedia(optionalClosedUnion); + if (optionalClosedMedia) { + optionalClosedMedia[1].schema = { + oneOf: [ + { additionalProperties: false, properties: { a: { type: "integer" } }, type: "object" }, + { additionalProperties: false, properties: { b: { type: "integer" } }, type: "object" }, + ], + }; + } + expect(sdkArguments(minimalDocument, optionalClosedUnion)).toMatchObject([{ name: "body", wholeBody: true }]); expect( curlCodeSample(minimalDocument, unionCreate, { body: unionBody, diff --git a/lib/openapi.ts b/lib/openapi.ts index 24c7435..542b0ac 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -451,9 +451,12 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) }); const incompatibleClosedBody = Boolean(closed?.length && new Set(closed).size > 1); const exclusiveBody = - variants?.length && - variants.every((variant) => variant?.required?.length) && - variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name))); + Boolean(bodySchema?.oneOf?.length) || + Boolean( + variants?.length && + variants.every((variant) => variant?.required?.length) && + variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name))), + ); if (body?.properties && !exclusiveBody && !incompatibleClosedBody) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; From 4994440db1b1df4861eadb68bc6ab02df9b46b86 Mon Sep 17 00:00:00 2001 From: UltralyticsAssistant Date: Thu, 13 Aug 2026 04:00:15 +0000 Subject: [PATCH 48/80] Auto-format by https://ultralytics.com/actions --- lib/openapi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index 542b0ac..a347668 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -454,8 +454,8 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) Boolean(bodySchema?.oneOf?.length) || Boolean( variants?.length && - variants.every((variant) => variant?.required?.length) && - variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name))), + variants.every((variant) => variant?.required?.length) && + variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name))), ); if (body?.properties && !exclusiveBody && !incompatibleClosedBody) { for (const [name, schema] of Object.entries(body.properties)) { From 9169c39d84df7a3ee5dfcb9a90c6da3eecdad272 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:01:27 +0200 Subject: [PATCH 49/80] Stabilize union body formatting --- lib/openapi.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/openapi.ts b/lib/openapi.ts index a347668..0adbe97 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -438,7 +438,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) .filter((description): description is string => Boolean(description)); const variantDescription = variantDescriptions?.join(" Or "); const body = structured ? objectSchema(document, bodySchema) : undefined; - const variants = union?.map((variant) => objectSchema(document, variant)); + const variants = union?.map((variant) => objectSchema(document, variant)) ?? []; const closed = bodySchema?.allOf?.flatMap((item) => { const branch = objectSchema(document, item); return branch?.additionalProperties === false @@ -452,11 +452,9 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) const incompatibleClosedBody = Boolean(closed?.length && new Set(closed).size > 1); const exclusiveBody = Boolean(bodySchema?.oneOf?.length) || - Boolean( - variants?.length && + (variants.length > 0 && variants.every((variant) => variant?.required?.length) && - variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name))), - ); + variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name)))); if (body?.properties && !exclusiveBody && !incompatibleClosedBody) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; From 49e5d57cad0743554d5aa51db59d48b7c8db7966 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:06:01 +0200 Subject: [PATCH 50/80] Preserve closed unions and literal patterns --- lib/generators/python.test.ts | 17 +++++++++++++++++ lib/openapi.ts | 22 +++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index d024ca7..b77b4b7 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -387,6 +387,13 @@ describe("Python generator", () => { }; } expect(sdkArguments(minimalDocument, optionalClosedUnion)).toMatchObject([{ name: "body", wholeBody: true }]); + const optionalClosedAnyOf = structuredClone(optionalClosedUnion); + const optionalClosedAnyOfMedia = requestMedia(optionalClosedAnyOf); + if (optionalClosedAnyOfMedia?.[1].schema?.oneOf) { + optionalClosedAnyOfMedia[1].schema.anyOf = optionalClosedAnyOfMedia[1].schema.oneOf; + delete optionalClosedAnyOfMedia[1].schema.oneOf; + } + expect(sdkArguments(minimalDocument, optionalClosedAnyOf)).toMatchObject([{ name: "body", wholeBody: true }]); expect( curlCodeSample(minimalDocument, unionCreate, { body: unionBody, @@ -683,6 +690,8 @@ describe("Python generator", () => { expect(schemaExample(document, { minLength: 4, pattern: "^\\d{3,5}$", type: "string" })).toBe("0000"); expect(schemaExample(document, { pattern: "^[0-9]*$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); + expect(schemaExample(document, { pattern: "^a+$", type: "string" })).toBe("a"); + expect(schemaExample(document, { pattern: "^a{3}$", type: "string" })).toBe("aaa"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); @@ -705,6 +714,14 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ ab: "example-ab", cd: "example-cd" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^a+$", type: "string" }, + type: "object", + }), + ).toEqual({ a: "example-a", aa: "example-aa" }); expect( schemaExample(document, { additionalProperties: { type: "string" }, diff --git a/lib/openapi.ts b/lib/openapi.ts index 0adbe97..c568de3 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -449,7 +449,18 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) ] : []; }); - const incompatibleClosedBody = Boolean(closed?.length && new Set(closed).size > 1); + const closedVariants = variants.flatMap((variant) => + variant?.additionalProperties === false + ? [ + Object.keys(variant.properties ?? {}) + .sort() + .join("\0"), + ] + : [], + ); + const incompatibleClosedBody = Boolean( + (closed?.length && new Set(closed).size > 1) || (closedVariants.length > 1 && new Set(closedVariants).size > 1), + ); const exclusiveBody = Boolean(bodySchema?.oneOf?.length) || (variants.length > 0 && @@ -899,6 +910,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return ["0".repeat(count)]; } + const repeatedLiteral = pattern.match(/^\^([A-Za-z0-9_-])(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); + if (repeatedLiteral?.[1]) { + const count = Math.min( + Math.max(Number(repeatedLiteral[2] ?? 1), schema.minLength ?? 1), + repeatedLiteral[3] ? Number(repeatedLiteral[3]) : Number.POSITIVE_INFINITY, + ); + return [repeatedLiteral[1].repeat(count)]; + } const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedRange?.[1]) { const count = Math.min( @@ -1421,6 +1440,7 @@ export function schemaExample( `${key}${index}`, `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`, + `${key}${key.at(-1)?.repeat(index - 1)}`, `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, ...(/^[0-9]+$/.test(key) ? [String(Number(key) + index - 1).padStart(key.length, "0")] : []), ...(/^[a-z]+$/.test(key) From f587d5402a62e7b134ed5ab3502d5b42c99b5ed0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:11:11 +0200 Subject: [PATCH 51/80] Complete constrained pattern examples --- lib/generators/python.test.ts | 12 ++++++++++++ lib/openapi.ts | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index b77b4b7..2943f06 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -692,6 +692,10 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[0-9A-F]{8}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^a+$", type: "string" })).toBe("a"); expect(schemaExample(document, { pattern: "^a{3}$", type: "string" })).toBe("aaa"); + expect(schemaExample(document, { pattern: "^v[0-9]+$", type: "string" })).toBe("v0"); + expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( + "2026-01-01T00:00:00.0Z", + ); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); @@ -722,6 +726,14 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ a: "example-a", aa: "example-aa" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^v[0-9]+$", type: "string" }, + type: "object", + }), + ).toEqual({ v0: "example-v0", v02: "example-v02" }); expect( schemaExample(document, { additionalProperties: { type: "string" }, diff --git a/lib/openapi.ts b/lib/openapi.ts index c568de3..1bad376 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -930,7 +930,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt /^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\](?:\{(\d+)(?:,\d*)?\}|[+*])\$$/, ); if (multiRange?.[1]) return [multiRange[1].repeat(Math.max(Number(multiRange[2] ?? 1), schema.minLength ?? 1))]; - const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\*\$$/); + const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\][*+]\$$/); if (suffixed?.[1] && suffixed[2]) { return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; } @@ -951,6 +951,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt }); }), ...(schema.format === "email" ? ["a@b.co"] : []), + ...(schema.format === "date-time" ? ["2026-01-01T00:00:00.0Z"] : []), ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), From 5dab1ced79ae3120c78b88b80ab86ef569659425 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:15:20 +0200 Subject: [PATCH 52/80] Generate constrained time examples --- lib/generators/python.test.ts | 5 +++++ lib/openapi.ts | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2943f06..1376a65 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -696,6 +696,11 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); + expect(schemaExample(document, { format: "date-time", minLength: 23, type: "string" })).toBe( + "2026-01-01T00:00:00.00Z", + ); + expect(schemaExample(document, { format: "time", minLength: 10, type: "string" })).toBe("12:00:00.0Z"); + expect(schemaExample(document, { format: "ipv6", maxLength: 3, type: "string" })).toBe("::1"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 1bad376..3845e3c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -951,9 +951,13 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt }); }), ...(schema.format === "email" ? ["a@b.co"] : []), - ...(schema.format === "date-time" ? ["2026-01-01T00:00:00.0Z"] : []), + ...(schema.format === "date-time" + ? [`2026-01-01T00:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 22) - 21))}Z`] + : []), + ...(schema.format === "time" ? [`12:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 11) - 10))}Z`] : []), ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), + ...(schema.format === "ipv6" ? ["::1"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), ].map(constrainLength); From 73b5da12c9491983e72c4d0cdc771c0303937713 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:20:08 +0200 Subject: [PATCH 53/80] Complete constrained object examples --- lib/generators/python.test.ts | 15 +++++++++++++++ lib/openapi.ts | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 1376a65..ab2f6c3 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -235,6 +235,12 @@ describe("Python generator", () => { ], }; expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); + createMedia[1].schema = { + additionalProperties: false, + allOf: [{ properties: { a: { type: "integer" } }, type: "object" }], + type: "object", + }; + expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); const minimalCurl = curlCodeSample(minimalDocument, create, { body: minimalBody, environment: "EXAMPLE_API_KEY", @@ -701,6 +707,7 @@ describe("Python generator", () => { ); expect(schemaExample(document, { format: "time", minLength: 10, type: "string" })).toBe("12:00:00.0Z"); expect(schemaExample(document, { format: "ipv6", maxLength: 3, type: "string" })).toBe("::1"); + expect(schemaExample(document, { format: "uri", maxLength: 8, type: "string" })).toBe("http://x"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); @@ -739,6 +746,14 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ v0: "example-v0", v02: "example-v02" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^[A-Z]{2}[0-9]{2}$", type: "string" }, + type: "object", + }), + ).toEqual({ AA00: "example-aa00", AA01: "example-aa01" }); expect( schemaExample(document, { additionalProperties: { type: "string" }, diff --git a/lib/openapi.ts b/lib/openapi.ts index 3845e3c..128e433 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -466,7 +466,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) (variants.length > 0 && variants.every((variant) => variant?.required?.length) && variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name)))); - if (body?.properties && !exclusiveBody && !incompatibleClosedBody) { + if (body?.properties && Object.keys(body.properties).length && !exclusiveBody && !incompatibleClosedBody) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; if (property.readOnly) continue; @@ -955,7 +955,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ? [`2026-01-01T00:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 22) - 21))}Z`] : []), ...(schema.format === "time" ? [`12:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 11) - 10))}Z`] : []), - ...(schema.format === "uri" || schema.format === "url" ? ["https://x.co"] : []), + ...(schema.format === "uri" || schema.format === "url" ? ["http://x", "https://x.co"] : []), ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), ...(schema.format === "ipv6" ? ["::1"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), @@ -1448,6 +1448,9 @@ export function schemaExample( `${key}${key.at(-1)?.repeat(index - 1)}`, `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, ...(/^[0-9]+$/.test(key) ? [String(Number(key) + index - 1).padStart(key.length, "0")] : []), + ...(/\d+$/.test(key) + ? [key.replace(/\d+$/, (digits) => String(Number(digits) + index - 1).padStart(digits.length, "0"))] + : []), ...(/^[a-z]+$/.test(key) ? [ `${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 97 + index) % 26))}`, From b5fd5bca65bb88b8a78ee846a46863274e7fbb4d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:24:03 +0200 Subject: [PATCH 54/80] Preserve composed string constraints --- lib/generators/python.test.ts | 2 ++ lib/openapi.ts | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index ab2f6c3..eb64bf0 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -699,6 +699,8 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^a+$", type: "string" })).toBe("a"); expect(schemaExample(document, { pattern: "^a{3}$", type: "string" })).toBe("aaa"); expect(schemaExample(document, { pattern: "^v[0-9]+$", type: "string" })).toBe("v0"); + expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); + expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); diff --git a/lib/openapi.ts b/lib/openapi.ts index 128e433..395c912 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -910,6 +910,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return ["0".repeat(count)]; } + const repeatedClass = pattern.match(/^\^\[([^\]]+)\](?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); + if (repeatedClass?.[1]) { + const count = Math.min( + Math.max(Number(repeatedClass[2] ?? 1), schema.minLength ?? 1), + repeatedClass[3] ? Number(repeatedClass[3]) : Number.POSITIVE_INFINITY, + ); + return [repeatedClass[1][0]?.repeat(count) ?? ""]; + } const repeatedLiteral = pattern.match(/^\^([A-Za-z0-9_-])(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedLiteral?.[1]) { const count = Math.min( @@ -1099,7 +1107,12 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam result.const === undefined && !result.enum?.length ) { - result.example = stringExample(result, name, patterns); + const formats = [...new Set(schemas.flatMap((schema) => (schema.format ? [schema.format] : [])))]; + result.example = + formats + .map((format) => stringExample({ ...result, format }, name, patterns)) + .find((candidate) => schemas.every((schema) => scalarMatches(candidate, schema))) ?? + stringExample(result, name, patterns); } return result; } From 7898d88023b596aa0f00e13c93bd99d6468e9a57 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:28:43 +0200 Subject: [PATCH 55/80] Generate compact format examples --- lib/generators/python.test.ts | 4 +++- lib/openapi.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index eb64bf0..4d398f3 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -709,7 +709,9 @@ describe("Python generator", () => { ); expect(schemaExample(document, { format: "time", minLength: 10, type: "string" })).toBe("12:00:00.0Z"); expect(schemaExample(document, { format: "ipv6", maxLength: 3, type: "string" })).toBe("::1"); - expect(schemaExample(document, { format: "uri", maxLength: 8, type: "string" })).toBe("http://x"); + expect(schemaExample(document, { format: "uri", maxLength: 8, type: "string" })).toBe("http:x"); + expect(schemaExample(document, { format: "uri", maxLength: 7, type: "string" })).toBe("http:x"); + expect(schemaExample(document, { format: "duration", minLength: 4, type: "string" })).toBe("P11D"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 395c912..277e46d 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -963,7 +963,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ? [`2026-01-01T00:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 22) - 21))}Z`] : []), ...(schema.format === "time" ? [`12:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 11) - 10))}Z`] : []), - ...(schema.format === "uri" || schema.format === "url" ? ["http://x", "https://x.co"] : []), + ...(schema.format === "duration" ? [`P${"1".repeat(Math.max(1, (schema.minLength ?? 3) - 2))}D`] : []), + ...(schema.format === "uri" || schema.format === "url" ? ["http:x", "http://x", "https://x.co"] : []), ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), ...(schema.format === "ipv6" ? ["::1"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), From cc456e0bef6e2f76bf1d23b0a040dcbe4baeb38a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:33:02 +0200 Subject: [PATCH 56/80] Complete bounded format examples --- lib/generators/python.test.ts | 5 +++++ lib/openapi.ts | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 4d398f3..518737d 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -699,6 +699,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^a+$", type: "string" })).toBe("a"); expect(schemaExample(document, { pattern: "^a{3}$", type: "string" })).toBe("aaa"); expect(schemaExample(document, { pattern: "^v[0-9]+$", type: "string" })).toBe("v0"); + expect(schemaExample(document, { pattern: "^key[0-9]{2}$", type: "string" })).toBe("key00"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( @@ -712,6 +713,10 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "uri", maxLength: 8, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "uri", maxLength: 7, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "duration", minLength: 4, type: "string" })).toBe("P11D"); + expect(schemaExample(document, { format: "ipv4", minLength: 12, type: "string" })).toBe("111.111.111.111"); + expect(schemaExample(document, { format: "ipv6", minLength: 20, type: "string" })).toBe( + "2001:0db8:0000:0000:0000:0000:0000:0001", + ); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 277e46d..076ba33 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -938,9 +938,15 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt /^\^\[([A-Za-z0-9])-[A-Za-z0-9](?:[A-Za-z0-9]-[A-Za-z0-9])+\](?:\{(\d+)(?:,\d*)?\}|[+*])\$$/, ); if (multiRange?.[1]) return [multiRange[1].repeat(Math.max(Number(multiRange[2] ?? 1), schema.minLength ?? 1))]; - const suffixed = pattern.match(/^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\][*+]\$$/); + const suffixed = pattern.match( + /^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,(\d*))?\}|[*+])\$$/, + ); if (suffixed?.[1] && suffixed[2]) { - return [`${suffixed[1]}${suffixed[2].repeat(Math.max(1, (schema.minLength ?? 0) - suffixed[1].length))}`]; + const count = Math.min( + Math.max(Number(suffixed[3] ?? 1), (schema.minLength ?? 0) - suffixed[1].length, 1), + suffixed[4] ? Number(suffixed[4]) : Number.POSITIVE_INFINITY, + ); + return [`${suffixed[1]}${suffixed[2].repeat(count)}`]; } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); @@ -966,7 +972,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(schema.format === "duration" ? [`P${"1".repeat(Math.max(1, (schema.minLength ?? 3) - 2))}D`] : []), ...(schema.format === "uri" || schema.format === "url" ? ["http:x", "http://x", "https://x.co"] : []), ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), - ...(schema.format === "ipv6" ? ["::1"] : []), + ...(schema.format === "ipv4" ? ["111.111.111.111"] : []), + ...(schema.format === "ipv6" ? ["::1", "2001:0db8:0000:0000:0000:0000:0000:0001"] : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), ].map(constrainLength); From 1424207c1b84ea5e2834656992f57fee146289b8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:38:34 +0200 Subject: [PATCH 57/80] Preserve union and bounded examples --- lib/generators/python.test.ts | 26 +++++++++++++++++++++++++- lib/openapi.ts | 29 ++++++++++++++++++----------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 518737d..b1f9303 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -400,6 +400,25 @@ describe("Python generator", () => { delete optionalClosedAnyOfMedia[1].schema.oneOf; } expect(sdkArguments(minimalDocument, optionalClosedAnyOf)).toMatchObject([{ name: "body", wholeBody: true }]); + const correlatedAnyOf = structuredClone(optionalClosedAnyOf); + const correlatedMedia = requestMedia(correlatedAnyOf); + if (correlatedMedia) { + correlatedMedia[1].schema = { + anyOf: [ + { + additionalProperties: false, + properties: { kind: { const: "a" }, value: { type: "integer" } }, + type: "object", + }, + { + additionalProperties: false, + properties: { kind: { const: "b" }, value: { type: "string" } }, + type: "object", + }, + ], + }; + } + expect(sdkArguments(minimalDocument, correlatedAnyOf)).toMatchObject([{ name: "body", wholeBody: true }]); expect( curlCodeSample(minimalDocument, unionCreate, { body: unionBody, @@ -702,6 +721,9 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^key[0-9]{2}$", type: "string" })).toBe("key00"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( + "0AAA", + ); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); @@ -713,7 +735,9 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "uri", maxLength: 8, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "uri", maxLength: 7, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "duration", minLength: 4, type: "string" })).toBe("P11D"); - expect(schemaExample(document, { format: "ipv4", minLength: 12, type: "string" })).toBe("111.111.111.111"); + expect(schemaExample(document, { format: "ipv4", minLength: 12, type: "string" })).toBe("1.11.111.111"); + expect(schemaExample(document, { format: "ipv4", maxLength: 8, minLength: 8, type: "string" })).toBe("1.1.1.11"); + expect(schemaExample(document, { format: "ipv6", maxLength: 10, minLength: 4, type: "string" })).toBe("2001:db8::"); expect(schemaExample(document, { format: "ipv6", minLength: 20, type: "string" })).toBe( "2001:0db8:0000:0000:0000:0000:0000:0001", ); diff --git a/lib/openapi.ts b/lib/openapi.ts index 076ba33..9af4ac3 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -458,9 +458,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) ] : [], ); - const incompatibleClosedBody = Boolean( - (closed?.length && new Set(closed).size > 1) || (closedVariants.length > 1 && new Set(closedVariants).size > 1), - ); + const incompatibleClosedBody = Boolean((closed?.length && new Set(closed).size > 1) || closedVariants.length); const exclusiveBody = Boolean(bodySchema?.oneOf?.length) || (variants.length > 0 && @@ -953,11 +951,9 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)\})/g)] : []; if (sequence?.includes("{") || ranges.length > 1 || sequence?.endsWith("+")) { - return [ - ranges - .map((match) => match[1]?.repeat(match[3] ? Number(match[3]) : Math.max(1, schema.minLength ?? 1))) - .join(""), - ]; + const counts = ranges.map((match) => (match[3] ? Number(match[3]) : 1)); + const extra = Math.max(0, (schema.minLength ?? 0) - counts.reduce((total, count) => total + count, 0)); + return [ranges.map((match, index) => match[1]?.repeat(counts[index] + (index ? extra : 0))).join("")]; } return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); @@ -971,9 +967,20 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(schema.format === "time" ? [`12:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 11) - 10))}Z`] : []), ...(schema.format === "duration" ? [`P${"1".repeat(Math.max(1, (schema.minLength ?? 3) - 2))}D`] : []), ...(schema.format === "uri" || schema.format === "url" ? ["http:x", "http://x", "https://x.co"] : []), - ...(schema.format === "ipv4" ? ["1.1.1.1"] : []), - ...(schema.format === "ipv4" ? ["111.111.111.111"] : []), - ...(schema.format === "ipv6" ? ["::1", "2001:0db8:0000:0000:0000:0000:0000:0001"] : []), + ...(schema.format === "ipv4" + ? ["1", "11", "111"].flatMap((a) => + ["1", "11", "111"].flatMap((b) => + ["1", "11", "111"].flatMap((c) => ["1", "11", "111"].map((d) => `${a}.${b}.${c}.${d}`)), + ), + ) + : []), + ...(schema.format === "ipv6" + ? [ + "::1", + ...Array.from({ length: 7 }, (_, index) => `${"0:".repeat(index + 1)}:`), + "2001:0db8:0000:0000:0000:0000:0000:0001", + ] + : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), ].map(constrainLength); From cd6eed817b36d3a081618fdf381c48fbe3460d2d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:43:19 +0200 Subject: [PATCH 58/80] Complete bounded composite examples --- lib/generators/python.test.ts | 14 ++++++++++---- lib/openapi.ts | 34 ++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index b1f9303..20b2ef5 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -722,8 +722,14 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( - "0AAA", + "000A", ); + expect(schemaExample(document, { maxLength: 5, minLength: 5, pattern: "^[0-9]+[A-Z]{2}$", type: "string" })).toBe( + "000AA", + ); + expect( + schemaExample(document, { maxLength: 6, minLength: 6, pattern: "^[A-Z]{2}[0-9]{2,4}$", type: "string" }), + ).toBe("AA0000"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); @@ -735,12 +741,12 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "uri", maxLength: 8, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "uri", maxLength: 7, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "duration", minLength: 4, type: "string" })).toBe("P11D"); + expect(schemaExample(document, { format: "email", maxLength: 7, minLength: 7, type: "string" })).toBe("a@b.cox"); expect(schemaExample(document, { format: "ipv4", minLength: 12, type: "string" })).toBe("1.11.111.111"); expect(schemaExample(document, { format: "ipv4", maxLength: 8, minLength: 8, type: "string" })).toBe("1.1.1.11"); expect(schemaExample(document, { format: "ipv6", maxLength: 10, minLength: 4, type: "string" })).toBe("2001:db8::"); - expect(schemaExample(document, { format: "ipv6", minLength: 20, type: "string" })).toBe( - "2001:0db8:0000:0000:0000:0000:0000:0001", - ); + expect(schemaExample(document, { format: "ipv6", maxLength: 4, minLength: 4, type: "string" })).toBe("0::1"); + expect(schemaExample(document, { format: "ipv6", minLength: 20, type: "string" })).toBe("0000:0000:0000:00::1"); expect(schemaExample(document, { pattern: "^[0-9A-F]{8,}$", type: "string" })).toBe("00000000"); expect(schemaExample(document, { pattern: "^[0-9]{3,}$", type: "string" })).toBe("000"); expect(schemaExample(document, { minLength: 4, pattern: "^[0-9]{3,5}$", type: "string" })).toBe("0000"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 9af4ac3..bcc7791 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -948,19 +948,28 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); - const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+\}))+?)\$$/)?.[1]; - const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)\})/g)] : []; + const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; + const ranges = sequence + ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)(?:,(\d*))?\})/g)] + : []; if (sequence?.includes("{") || ranges.length > 1 || sequence?.endsWith("+")) { const counts = ranges.map((match) => (match[3] ? Number(match[3]) : 1)); - const extra = Math.max(0, (schema.minLength ?? 0) - counts.reduce((total, count) => total + count, 0)); - return [ranges.map((match, index) => match[1]?.repeat(counts[index] + (index ? extra : 0))).join("")]; + let extra = Math.max(0, (schema.minLength ?? 0) - counts.reduce((total, count) => total + count, 0)); + for (const [index, match] of ranges.entries()) { + const maximum = + match[2] === "+" || match[4] === "" ? Number.POSITIVE_INFINITY : Number(match[4] ?? match[3]); + const added = Math.min(extra, maximum - counts[index]); + counts[index] += added; + extra -= added; + } + return [ranges.map((match, index) => match[1]?.repeat(counts[index])).join("")]; } return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); return match?.[1] ? [match[1]] : []; }); }), - ...(schema.format === "email" ? ["a@b.co"] : []), + ...(schema.format === "email" ? ["a@b.co", `${"a".repeat(Math.max(1, (schema.minLength ?? 6) - 5))}@b.co`] : []), ...(schema.format === "date-time" ? [`2026-01-01T00:00:00.${"0".repeat(Math.max(1, (schema.minLength ?? 22) - 21))}Z`] : []), @@ -977,7 +986,20 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ...(schema.format === "ipv6" ? [ "::1", - ...Array.from({ length: 7 }, (_, index) => `${"0:".repeat(index + 1)}:`), + ...Array.from({ length: 37 }, (_, index) => index + 3).flatMap((length) => { + for (let groups = 1; groups <= 7; groups += 1) { + let digits = length - (groups - 1) - 3; + if (digits < groups || digits > groups * 4) continue; + const widths = Array.from({ length: groups }, () => 1); + for (let group = 0; digits > groups && group < groups; group += 1) { + const added = Math.min(3, digits - groups); + widths[group] += added; + digits -= added; + } + return [`${widths.map((width) => "0".repeat(width)).join(":")}::1`]; + } + return []; + }), "2001:0db8:0000:0000:0000:0000:0000:0001", ] : []), From 98dce5d02f7e1f96782d9eccc4f4d683dcc7369b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:47:47 +0200 Subject: [PATCH 59/80] Retain union request contracts --- lib/generators/python.test.ts | 13 +++++++++++++ lib/openapi.ts | 24 +++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 20b2ef5..eaad087 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -419,6 +419,10 @@ describe("Python generator", () => { }; } expect(sdkArguments(minimalDocument, correlatedAnyOf)).toMatchObject([{ name: "body", wholeBody: true }]); + if (correlatedMedia?.[1].schema?.anyOf) { + for (const variant of correlatedMedia[1].schema.anyOf) delete variant.additionalProperties; + } + expect(sdkArguments(minimalDocument, correlatedAnyOf)).toMatchObject([{ name: "body", wholeBody: true }]); expect( curlCodeSample(minimalDocument, unionCreate, { body: unionBody, @@ -719,6 +723,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^a{3}$", type: "string" })).toBe("aaa"); expect(schemaExample(document, { pattern: "^v[0-9]+$", type: "string" })).toBe("v0"); expect(schemaExample(document, { pattern: "^key[0-9]{2}$", type: "string" })).toBe("key00"); + expect(schemaExample(document, { pattern: "^item-\\d+$", type: "string" })).toBe("item-0"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( @@ -793,6 +798,14 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ AA00: "example-aa00", AA01: "example-aa01" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^[A-Z]{2}[a-z]{2}$", type: "string" }, + type: "object", + }), + ).toEqual({ AAaa: "example-aaaa", AAac: "example-aaac" }); expect( schemaExample(document, { additionalProperties: { type: "string" }, diff --git a/lib/openapi.ts b/lib/openapi.ts index bcc7791..e078709 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -459,11 +459,7 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) : [], ); const incompatibleClosedBody = Boolean((closed?.length && new Set(closed).size > 1) || closedVariants.length); - const exclusiveBody = - Boolean(bodySchema?.oneOf?.length) || - (variants.length > 0 && - variants.every((variant) => variant?.required?.length) && - variants.some((variant) => variant?.required?.some((name) => !body?.required?.includes(name)))); + const exclusiveBody = Boolean(union?.length); if (body?.properties && Object.keys(body.properties).length && !exclusiveBody && !incompatibleClosedBody) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; @@ -946,6 +942,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${suffixed[1]}${suffixed[2].repeat(count)}`]; } + const digitSuffix = pattern.match(/^\^([A-Za-z0-9._-]+)\\d(?:\{(\d+)(?:,(\d*))?\}|[*+])\$$/); + if (digitSuffix?.[1]) { + const count = Math.min( + Math.max(Number(digitSuffix[2] ?? 1), (schema.minLength ?? 0) - digitSuffix[1].length, 1), + digitSuffix[3] ? Number(digitSuffix[3]) : Number.POSITIVE_INFINITY, + ); + return [`${digitSuffix[1]}${"0".repeat(count)}`]; + } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; @@ -1496,6 +1500,16 @@ export function schemaExample( `${key}${"X".repeat(index - 1)}`, `${key}${"x".repeat(index - 1)}`, `${key}${key.at(-1)?.repeat(index - 1)}`, + ...(/[a-z]$/.test(key) + ? [ + `${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 97 + index) % 26))}`, + ] + : []), + ...(/[A-Z]$/.test(key) + ? [ + `${key.slice(0, -1)}${String.fromCharCode(65 + ((key.charCodeAt(key.length - 1) - 65 + index) % 26))}`, + ] + : []), `${key.slice(0, -1)}${String.fromCharCode(65 + (index % 26))}`, ...(/^[0-9]+$/.test(key) ? [String(Number(key) + index - 1).padStart(key.length, "0")] : []), ...(/\d+$/.test(key) From 04667bdc8b96e04c53720f5c11d8adcd30483764 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:51:05 +0200 Subject: [PATCH 60/80] Generate bounded host and mixed patterns --- lib/generators/python.test.ts | 6 ++++++ lib/openapi.ts | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index eaad087..cb6c173 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -735,6 +735,9 @@ describe("Python generator", () => { expect( schemaExample(document, { maxLength: 6, minLength: 6, pattern: "^[A-Z]{2}[0-9]{2,4}$", type: "string" }), ).toBe("AA0000"); + expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[A-Z]{2}\\d{2}$", type: "string" })).toBe( + "AA00", + ); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); @@ -747,6 +750,9 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "uri", maxLength: 7, type: "string" })).toBe("http:x"); expect(schemaExample(document, { format: "duration", minLength: 4, type: "string" })).toBe("P11D"); expect(schemaExample(document, { format: "email", maxLength: 7, minLength: 7, type: "string" })).toBe("a@b.cox"); + expect(schemaExample(document, { format: "hostname", maxLength: 72, minLength: 72, type: "string" })).toHaveLength( + 72, + ); expect(schemaExample(document, { format: "ipv4", minLength: 12, type: "string" })).toBe("1.11.111.111"); expect(schemaExample(document, { format: "ipv4", maxLength: 8, minLength: 8, type: "string" })).toBe("1.1.1.11"); expect(schemaExample(document, { format: "ipv6", maxLength: 10, minLength: 4, type: "string" })).toBe("2001:db8::"); diff --git a/lib/openapi.ts b/lib/openapi.ts index e078709..68cd22d 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -952,6 +952,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); + const mixedSequence = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\d\{(\d+)\}\$$/); + if (mixedSequence?.[1]) { + return [`${mixedSequence[1].repeat(Number(mixedSequence[2]))}${"0".repeat(Number(mixedSequence[3]))}`]; + } const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)(?:,(\d*))?\})/g)] @@ -1007,6 +1011,17 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt "2001:0db8:0000:0000:0000:0000:0000:0001", ] : []), + ...(schema.format === "hostname" + ? Array.from({ length: 253 }, (_, index) => index + 1).map((length) => { + const labels = Math.ceil(length / 64); + let characters = length - labels + 1; + return Array.from({ length: labels }, (_, index) => { + const width = Math.min(63, characters - (labels - index - 1)); + characters -= width; + return "a".repeat(width); + }).join("."); + }) + : []), ...(key === "sourceurl" ? ["https://example.com/dataset.zip"] : []), ...(key === "region" ? ["us-east-1"] : []), ].map(constrainLength); From a022b8c46e93788320becedd86597454f2e0e6f8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:53:50 +0200 Subject: [PATCH 61/80] Complete bounded host examples --- lib/generators/python.test.ts | 4 ++++ lib/openapi.ts | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index cb6c173..218bfa8 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -726,6 +726,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^item-\\d+$", type: "string" })).toBe("item-0"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); + expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( "000A", ); @@ -753,6 +754,9 @@ describe("Python generator", () => { expect(schemaExample(document, { format: "hostname", maxLength: 72, minLength: 72, type: "string" })).toHaveLength( 72, ); + expect( + schemaExample(document, { format: "hostname", maxLength: 128, minLength: 128, type: "string" }), + ).toHaveLength(128); expect(schemaExample(document, { format: "ipv4", minLength: 12, type: "string" })).toBe("1.11.111.111"); expect(schemaExample(document, { format: "ipv4", maxLength: 8, minLength: 8, type: "string" })).toBe("1.1.1.11"); expect(schemaExample(document, { format: "ipv6", maxLength: 10, minLength: 4, type: "string" })).toBe("2001:db8::"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 68cd22d..7c961ec 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -910,7 +910,8 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt Math.max(Number(repeatedClass[2] ?? 1), schema.minLength ?? 1), repeatedClass[3] ? Number(repeatedClass[3]) : Number.POSITIVE_INFINITY, ); - return [repeatedClass[1][0]?.repeat(count) ?? ""]; + const member = repeatedClass[1].startsWith("\\d") ? "0" : repeatedClass[1][0]; + return [member?.repeat(count) ?? ""]; } const repeatedLiteral = pattern.match(/^\^([A-Za-z0-9_-])(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedLiteral?.[1]) { @@ -1013,7 +1014,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt : []), ...(schema.format === "hostname" ? Array.from({ length: 253 }, (_, index) => index + 1).map((length) => { - const labels = Math.ceil(length / 64); + const labels = Math.ceil((length + 1) / 64); let characters = length - labels + 1; return Array.from({ length: labels }, (_, index) => { const width = Math.min(63, characters - (labels - index - 1)); From f035dad84e194f7259ea3e25b2db2f109803f9ed Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 06:58:34 +0200 Subject: [PATCH 62/80] Constrain fallback examples --- lib/generators/python.test.ts | 5 ++++- lib/openapi.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 218bfa8..4b2496e 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -534,7 +534,7 @@ describe("Python generator", () => { type: "string", }), ).toBe("2027-01-01"); - expect(schemaExample(document, { format: "date-time", maxLength: 10, type: "string" })).toBe(""); + expect(schemaExample(document, { format: "date-time", maxLength: 10, type: "string" })).toBe(" { expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); + expect( + schemaExample(document, { maxLength: 7, pattern: "^(?:[A-Z]{2}\\.){2}[0-9]$", type: "string" }), + ).toHaveLength(7); expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( "000A", ); diff --git a/lib/openapi.ts b/lib/openapi.ts index 7c961ec..c7af4e8 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1031,10 +1031,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt (candidate) => stringFormatMatches(candidate, schema.format) && expressions.every((expression) => expression.test(candidate)), - ) ?? `<${schema.format ?? "pattern"} value>` + ) ?? constrainLength(`<${schema.format ?? "pattern"} value>`) ); } catch { - return ""; + return constrainLength(""); } } From fd4a097b5248053cebb8aaac4bda69a21d3bf681 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:02:24 +0200 Subject: [PATCH 63/80] Preserve constrained composed bodies --- lib/generators/python.test.ts | 9 +++++++++ lib/openapi.ts | 22 +++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 4b2496e..560b389 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -241,6 +241,13 @@ describe("Python generator", () => { type: "object", }; expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); + createMedia[1].schema = { + allOf: [ + { additionalProperties: { type: "string" }, properties: { a: { type: "string" } }, type: "object" }, + { additionalProperties: { type: "number" }, properties: { b: { type: "number" } }, type: "object" }, + ], + }; + expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); const minimalCurl = curlCodeSample(minimalDocument, create, { body: minimalBody, environment: "EXAMPLE_API_KEY", @@ -742,6 +749,8 @@ describe("Python generator", () => { expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[A-Z]{2}\\d{2}$", type: "string" })).toBe( "AA00", ); + expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d{2,4}$", type: "string" })).toBe("AA00"); + expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d+$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); diff --git a/lib/openapi.ts b/lib/openapi.ts index c7af4e8..1c79018 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -459,8 +459,17 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) : [], ); const incompatibleClosedBody = Boolean((closed?.length && new Set(closed).size > 1) || closedVariants.length); + const constrainedComposedBody = Boolean( + bodySchema?.allOf?.some((item) => typeof objectSchema(document, item)?.additionalProperties === "object"), + ); const exclusiveBody = Boolean(union?.length); - if (body?.properties && Object.keys(body.properties).length && !exclusiveBody && !incompatibleClosedBody) { + if ( + body?.properties && + Object.keys(body.properties).length && + !exclusiveBody && + !incompatibleClosedBody && + !constrainedComposedBody + ) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; if (property.readOnly) continue; @@ -953,9 +962,16 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); - const mixedSequence = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\d\{(\d+)\}\$$/); + const mixedSequence = pattern.match( + /^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\d(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/, + ); if (mixedSequence?.[1]) { - return [`${mixedSequence[1].repeat(Number(mixedSequence[2]))}${"0".repeat(Number(mixedSequence[3]))}`]; + const prefix = mixedSequence[1].repeat(Number(mixedSequence[2])); + const count = Math.min( + Math.max(Number(mixedSequence[3] ?? 1), (schema.minLength ?? 0) - prefix.length, 1), + mixedSequence[4] ? Number(mixedSequence[4]) : Number.POSITIVE_INFINITY, + ); + return [`${prefix}${"0".repeat(count)}`]; } const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; const ranges = sequence From 4967e0ecd47d568e9995aa14376bd33cb2a81c8d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:06:41 +0200 Subject: [PATCH 64/80] Generate runnable path examples --- lib/generators/python.test.ts | 4 ++-- lib/openapi.ts | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 560b389..2b11da2 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -470,8 +470,8 @@ describe("Python generator", () => { const pathParameter = retrieve.parameters?.find((parameter) => parameter.in === "path"); if (pathParameter) pathParameter.schema = { type: "integer" }; const retrieveCurl = curlCodeSample(document, retrieve, { origin: "https://docs.example.com" }); - expect(retrieveCurl).toStartWith("curl -g "); - expect(retrieveCurl).toContain("/widgets/{widgetId}"); + expect(retrieveCurl).toStartWith("curl "); + expect(retrieveCurl).toContain("/widgets/1"); expect( curlCodeSample(document, retrieve, { origin: "https://docs.example.com", diff --git a/lib/openapi.ts b/lib/openapi.ts index 1c79018..9c8a341 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1898,16 +1898,6 @@ export function curlCodeSample( }; let path = operation.path; for (const parameter of (operation.parameters ?? []).filter((item) => item.in === "path")) { - const schema = resolveSchema(document, parameter.schema); - const supplied = values[`${parameter.in}:${parameter.name}`]; - const explicit = - schema?.example !== undefined || - schema?.default !== undefined || - schema?.const !== undefined || - Boolean(schema?.enum?.length); - if (!explicit && supplied === undefined) { - continue; - } const value = serializeSimplePath(parameterValueOrExample(parameter), parameter.explode, parameter.allowReserved); if (value) path = path.replace(`{${parameter.name}}`, value); } From 9af840169c37381eaf2bd3e1786325a40c36d09d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:16:13 +0200 Subject: [PATCH 65/80] Complete runnable pattern paths --- lib/generators/python.test.ts | 3 ++- lib/openapi.ts | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2b11da2..9e0945b 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -483,7 +483,7 @@ describe("Python generator", () => { origin: "https://docs.example.com", values: { "path:widgetId": "" }, }), - ).toContain("/widgets/{widgetId}"); + ).toContain("/widgets/"); const session = getOperations(document).find((operation) => operation.path === "/sessions"); expect(session).toBeDefined(); if (!session) return; @@ -731,6 +731,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^v[0-9]+$", type: "string" })).toBe("v0"); expect(schemaExample(document, { pattern: "^key[0-9]{2}$", type: "string" })).toBe("key00"); expect(schemaExample(document, { pattern: "^item-\\d+$", type: "string" })).toBe("item-0"); + expect(schemaExample(document, { pattern: "^foo[0-9]{2}bar$", type: "string" })).toBe("foo00bar"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 9c8a341..f25fdbc 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -960,6 +960,12 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${digitSuffix[1]}${"0".repeat(count)}`]; } + const wrappedRange = pattern.match( + /^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}([A-Za-z0-9._-]+)\$$/, + ); + if (wrappedRange?.[1] && wrappedRange[2]) { + return [`${wrappedRange[1]}${wrappedRange[2].repeat(Number(wrappedRange[3]))}${wrappedRange[4]}`]; + } const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const mixedSequence = pattern.match( @@ -1899,7 +1905,7 @@ export function curlCodeSample( let path = operation.path; for (const parameter of (operation.parameters ?? []).filter((item) => item.in === "path")) { const value = serializeSimplePath(parameterValueOrExample(parameter), parameter.explode, parameter.allowReserved); - if (value) path = path.replace(`{${parameter.name}}`, value); + path = path.replace(`{${parameter.name}}`, value); } const query = (operation.parameters ?? []) .filter((parameter) => parameter.in === "query" && (parameter.required || values[`query:${parameter.name}`])) From 4a4f0564f19b97ac1477ff5db338e6268f498d9b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:20:32 +0200 Subject: [PATCH 66/80] Preserve bounded numeric examples --- lib/generators/python.test.ts | 5 +++++ lib/openapi.ts | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 9e0945b..89067d0 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -752,6 +752,8 @@ describe("Python generator", () => { ); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d{2,4}$", type: "string" })).toBe("AA00"); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d+$", type: "string" })).toBe("AA0"); + expect(schemaExample(document, { pattern: "^[A-Z]+\\d+$", type: "string" })).toBe("A0"); + expect(schemaExample(document, { pattern: "^\\d+[A-Z]+$", type: "string" })).toBe("0A"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); @@ -1036,6 +1038,9 @@ describe("Python generator", () => { }), ).toEqual({ AAAAAAAA: "example-aaaaaaaa" }); expect(schemaExample(document, { exclusiveMinimum: 1, minimum: 5, type: "number" })).toBe(5); + expect(schemaExample(document, { minimum: Number.MAX_SAFE_INTEGER, multipleOf: 1, type: "integer" })).toBe( + Number.MAX_SAFE_INTEGER, + ); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index f25fdbc..8918ec2 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -979,6 +979,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${prefix}${"0".repeat(count)}`]; } + const variableMixed = pattern.match( + /^\^(?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\+|\\d\+\[([A-Za-z0-9])-[A-Za-z0-9]\]\+)\$$/, + ); + if (variableMixed) return [variableMixed[1] ? `${variableMixed[1]}0` : `0${variableMixed[2]}`]; const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)(?:,(\d*))?\})/g)] @@ -1460,7 +1464,8 @@ export function schemaExample( ]; const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; - const round = (candidate: number) => Number(candidate.toPrecision(15)); + const round = (candidate: number) => + Number.isSafeInteger(candidate) ? candidate : Number(candidate.toPrecision(15)); if (type === "number" && !multiple && minimum?.exclusive && maximum?.exclusive && !scalarMatches(1, schema)) { return round((minimum.value + maximum.value) / 2); } From 0cfb0a0958ac92d59650bcd3af5e1dc96035c409 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:25:25 +0200 Subject: [PATCH 67/80] Preserve bounded composed examples --- lib/generators/python.test.ts | 28 ++++++++++++++++++++++++++++ lib/openapi.ts | 9 +++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 89067d0..15ba134 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -265,6 +265,24 @@ describe("Python generator", () => { oneOf: [{ example: { name: "authored", optional: "preserved" }, type: "object" }], }; expect(requestBodyExample(minimalDocument, create)).toContain('"optional": "preserved"'); + createMedia[1].schema = { + oneOf: [ + { + additionalProperties: false, + example: { kind: "a" }, + properties: { kind: { const: "a" } }, + type: "object", + }, + { + additionalProperties: false, + properties: { b: { type: "integer" } }, + required: ["b"], + type: "object", + }, + ], + required: ["b"], + }; + expect(requestBodyExample(minimalDocument, create)).toBe('{\n "b": 1\n}'); createMedia[1].schema = { allOf: [{ oneOf: [{ example: { name: "authored", optional: "preserved" }, type: "object" }] }], }; @@ -754,6 +772,8 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d+$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d+$", type: "string" })).toBe("A0"); expect(schemaExample(document, { pattern: "^\\d+[A-Z]+$", type: "string" })).toBe("0A"); + expect(schemaExample(document, { pattern: "^[A-Z]+\\d{2}$", type: "string" })).toBe("A00"); + expect(schemaExample(document, { pattern: "^\\d{2}[A-Z]+$", type: "string" })).toBe("00A"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); @@ -1041,6 +1061,14 @@ describe("Python generator", () => { expect(schemaExample(document, { minimum: Number.MAX_SAFE_INTEGER, multipleOf: 1, type: "integer" })).toBe( Number.MAX_SAFE_INTEGER, ); + expect( + schemaExample(document, { + maximum: Number.MAX_SAFE_INTEGER + 1, + minimum: Number.MAX_SAFE_INTEGER, + multipleOf: 2, + type: "integer", + }), + ).toBe(Number.MAX_SAFE_INTEGER + 1); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 8918ec2..b5c6000 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -983,6 +983,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt /^\^(?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\+|\\d\+\[([A-Za-z0-9])-[A-Za-z0-9]\]\+)\$$/, ); if (variableMixed) return [variableMixed[1] ? `${variableMixed[1]}0` : `0${variableMixed[2]}`]; + const rangeThenDigits = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\{(\d+)\}\$$/); + if (rangeThenDigits?.[1]) return [`${rangeThenDigits[1]}${"0".repeat(Number(rangeThenDigits[2]))}`]; + const digitsThenRange = pattern.match(/^\^\\d\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\$$/); + if (digitsThenRange?.[2]) return [`${"0".repeat(Number(digitsThenRange[1]))}${digitsThenRange[2]}`]; const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)(?:,(\d*))?\})/g)] @@ -1465,7 +1469,7 @@ export function schemaExample( const minimum = minimums.sort((a, b) => b.value - a.value || Number(b.exclusive) - Number(a.exclusive))[0]; const maximum = maximums.sort((a, b) => a.value - b.value || Number(b.exclusive) - Number(a.exclusive))[0]; const round = (candidate: number) => - Number.isSafeInteger(candidate) ? candidate : Number(candidate.toPrecision(15)); + type === "integer" && Number.isInteger(candidate) ? candidate : Number(candidate.toPrecision(15)); if (type === "number" && !multiple && minimum?.exclusive && maximum?.exclusive && !scalarMatches(1, schema)) { return round((minimum.value + maximum.value) / 2); } @@ -1843,7 +1847,7 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper else if (named.length) example = {}; } if (authored.nested && authored.value !== undefined) { - example = + const combined = authored.value !== null && typeof authored.value === "object" && !Array.isArray(authored.value) && @@ -1852,6 +1856,7 @@ export function requestBodyExample(document: OpenApiDocument, operation: ApiOper !Array.isArray(example) ? { ...example, ...authored.value } : authored.value; + if (schemaMatches(document, combined, request[1].schema ?? {})) example = combined; } return request[0].startsWith("text/") && typeof example === "string" ? example : JSON.stringify(example, null, 2); } From a1d87489e461feca35b3301dbc7d30ee9e696423 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:29:32 +0200 Subject: [PATCH 68/80] Complete bounded mixed patterns --- lib/generators/python.test.ts | 1 + lib/openapi.ts | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 15ba134..e3ff1aa 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -774,6 +774,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^\\d+[A-Z]+$", type: "string" })).toBe("0A"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d{2}$", type: "string" })).toBe("A00"); expect(schemaExample(document, { pattern: "^\\d{2}[A-Z]+$", type: "string" })).toBe("00A"); + expect(schemaExample(document, { pattern: "^\\d{2}[A-Z]{2,4}$", type: "string" })).toBe("00AA"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( "2026-01-01T00:00:00.0Z", ); diff --git a/lib/openapi.ts b/lib/openapi.ts index b5c6000..a657a7b 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -987,6 +987,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt if (rangeThenDigits?.[1]) return [`${rangeThenDigits[1]}${"0".repeat(Number(rangeThenDigits[2]))}`]; const digitsThenRange = pattern.match(/^\^\\d\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\$$/); if (digitsThenRange?.[2]) return [`${"0".repeat(Number(digitsThenRange[1]))}${digitsThenRange[2]}`]; + const digitsThenBoundedRange = pattern.match( + /^\^\\d\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)(?:,(\d*))?\}\$$/, + ); + if (digitsThenBoundedRange?.[2]) { + return [ + `${"0".repeat(Number(digitsThenBoundedRange[1]))}${digitsThenBoundedRange[2].repeat(Number(digitsThenBoundedRange[3]))}`, + ]; + } const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; const ranges = sequence ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)(?:,(\d*))?\})/g)] From 55585eddac02c36bebd7e13b872e7a1b1014babc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:33:33 +0200 Subject: [PATCH 69/80] Cover zero numeric bounds --- lib/generators/python.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index e3ff1aa..53e0feb 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -1109,6 +1109,7 @@ describe("Python generator", () => { }), ).toBe(1.2); expect(schemaExample(document, { minimum: -Number.MAX_SAFE_INTEGER, type: "integer" })).toBe(1); + expect(schemaExample(document, { maximum: 0, type: "integer" })).toBe(0); expect(schemaExample(document, { maximum: 1.9, minimum: 1.5, type: ["integer", "number"] })).toBe(1.5); expect(schemaExample(document, { multipleOf: 0.3, type: "integer" })).toBe(3); expect( From d0c6e8c2fee6358a8fd08b46b272af923e2f9f32 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:37:19 +0200 Subject: [PATCH 70/80] Complete quantified pattern examples --- lib/generators/python.test.ts | 2 ++ lib/openapi.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 53e0feb..5bfb501 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -750,6 +750,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^key[0-9]{2}$", type: "string" })).toBe("key00"); expect(schemaExample(document, { pattern: "^item-\\d+$", type: "string" })).toBe("item-0"); expect(schemaExample(document, { pattern: "^foo[0-9]{2}bar$", type: "string" })).toBe("foo00bar"); + expect(schemaExample(document, { pattern: "^foo\\d{2}bar$", type: "string" })).toBe("foo00bar"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); @@ -773,6 +774,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[A-Z]+\\d+$", type: "string" })).toBe("A0"); expect(schemaExample(document, { pattern: "^\\d+[A-Z]+$", type: "string" })).toBe("0A"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d{2}$", type: "string" })).toBe("A00"); + expect(schemaExample(document, { pattern: "^[A-Z]{2,4}\\d+$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { pattern: "^\\d{2}[A-Z]+$", type: "string" })).toBe("00A"); expect(schemaExample(document, { pattern: "^\\d{2}[A-Z]{2,4}$", type: "string" })).toBe("00AA"); expect(schemaExample(document, { format: "date-time", minLength: 21, type: "string" })).toBe( diff --git a/lib/openapi.ts b/lib/openapi.ts index a657a7b..b37e5ed 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -966,6 +966,9 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt if (wrappedRange?.[1] && wrappedRange[2]) { return [`${wrappedRange[1]}${wrappedRange[2].repeat(Number(wrappedRange[3]))}${wrappedRange[4]}`]; } + const wrappedDigits = pattern.match(/^\^([A-Za-z0-9._-]+)\\d\{(\d+)\}([A-Za-z0-9._-]+)\$$/); + if (wrappedDigits?.[1]) + return [`${wrappedDigits[1]}${"0".repeat(Number(wrappedDigits[2]))}${wrappedDigits[3]}`]; const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const mixedSequence = pattern.match( @@ -985,6 +988,10 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt if (variableMixed) return [variableMixed[1] ? `${variableMixed[1]}0` : `0${variableMixed[2]}`]; const rangeThenDigits = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\{(\d+)\}\$$/); if (rangeThenDigits?.[1]) return [`${rangeThenDigits[1]}${"0".repeat(Number(rangeThenDigits[2]))}`]; + const boundedRangeThenDigits = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)(?:,(\d*))?\}\\d\+\$$/); + if (boundedRangeThenDigits?.[1]) { + return [`${boundedRangeThenDigits[1].repeat(Number(boundedRangeThenDigits[2]))}0`]; + } const digitsThenRange = pattern.match(/^\^\\d\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\$$/); if (digitsThenRange?.[2]) return [`${"0".repeat(Number(digitsThenRange[1]))}${digitsThenRange[2]}`]; const digitsThenBoundedRange = pattern.match( From f58766bdd25b20b8df955180d675c2556ec8b38e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:41:34 +0200 Subject: [PATCH 71/80] Complete grouped pattern examples --- lib/generators/python.test.ts | 13 ++++++++++--- lib/openapi.ts | 13 +++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 5bfb501..8672c05 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -754,9 +754,9 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); - expect( - schemaExample(document, { maxLength: 7, pattern: "^(?:[A-Z]{2}\\.){2}[0-9]$", type: "string" }), - ).toHaveLength(7); + expect(schemaExample(document, { maxLength: 7, pattern: "^(?:[A-Z]{2}\\.){2}[0-9]$", type: "string" })).toBe( + "AA.AA.0", + ); expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( "000A", ); @@ -1072,6 +1072,13 @@ describe("Python generator", () => { type: "integer", }), ).toBe(Number.MAX_SAFE_INTEGER + 1); + expect( + schemaExample(document, { + exclusiveMaximum: 100000000000001, + exclusiveMinimum: 100000000000000, + type: "number", + }), + ).toBe(100000000000000.5); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index b37e5ed..3123a35 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -972,7 +972,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const mixedSequence = pattern.match( - /^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\d(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/, + /^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)(?:,\d*)?\}\\d(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/, ); if (mixedSequence?.[1]) { const prefix = mixedSequence[1].repeat(Number(mixedSequence[2])); @@ -982,6 +982,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${prefix}${"0".repeat(count)}`]; } + const repeatedGroup = pattern.match( + /^\^\(\?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\\.\)\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\$$/, + ); + if (repeatedGroup?.[1] && repeatedGroup[4]) { + return [ + `${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(Number(repeatedGroup[3]))}${repeatedGroup[4]}`, + ]; + } const variableMixed = pattern.match( /^\^(?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\+|\\d\+\[([A-Za-z0-9])-[A-Za-z0-9]\]\+)\$$/, ); @@ -1486,7 +1494,8 @@ export function schemaExample( const round = (candidate: number) => type === "integer" && Number.isInteger(candidate) ? candidate : Number(candidate.toPrecision(15)); if (type === "number" && !multiple && minimum?.exclusive && maximum?.exclusive && !scalarMatches(1, schema)) { - return round((minimum.value + maximum.value) / 2); + const midpoint = (minimum.value + maximum.value) / 2; + return scalarMatches(midpoint, schema) ? midpoint : round(midpoint); } let value = 1; if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { From 34898291eb66ba570d8d9be91be005f605300cc0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:47:50 +0200 Subject: [PATCH 72/80] Cover remaining schema example edge cases --- lib/generators/python.test.ts | 23 ++++++++++++++++++++++ lib/openapi.ts | 37 +++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 8672c05..19a2cce 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -235,6 +235,18 @@ describe("Python generator", () => { ], }; expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); + createMedia[1].schema = { + allOf: [ + { + oneOf: [ + { properties: { a: { type: "integer" } }, required: ["a"], type: "object" }, + { properties: { b: { type: "integer" } }, required: ["b"], type: "object" }, + ], + }, + ], + type: "object", + }; + expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); createMedia[1].schema = { additionalProperties: false, allOf: [{ properties: { a: { type: "integer" } }, type: "object" }], @@ -757,6 +769,10 @@ describe("Python generator", () => { expect(schemaExample(document, { maxLength: 7, pattern: "^(?:[A-Z]{2}\\.){2}[0-9]$", type: "string" })).toBe( "AA.AA.0", ); + expect(schemaExample(document, { pattern: "^(?:[A-Z]{2}\\.){2,3}[0-9]$", type: "string" })).toBe("AA.AA.0"); + expect(schemaExample(document, { pattern: "^\\d{0}$", type: "string" })).toBe(""); + expect(schemaExample(document, { pattern: "^[A-Z]{0}$", type: "string" })).toBe(""); + expect(schemaExample(document, { pattern: "^a{0}$", type: "string" })).toBe(""); expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[0-9]+[A-Z]+$", type: "string" })).toBe( "000A", ); @@ -1079,6 +1095,13 @@ describe("Python generator", () => { type: "number", }), ).toBe(100000000000000.5); + expect( + schemaExample(document, { + exclusiveMaximum: 10000000000000002, + exclusiveMinimum: 10000000000000000, + type: "number", + }), + ).toBeNull(); expect( schemaExample(document, { allOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 3123a35..e6b03bf 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -462,7 +462,22 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) const constrainedComposedBody = Boolean( bodySchema?.allOf?.some((item) => typeof objectSchema(document, item)?.additionalProperties === "object"), ); - const exclusiveBody = Boolean(union?.length); + const containsExclusiveUnion = (input: JsonSchema | undefined, depth = 0): boolean => { + const schema = resolveSchema(document, input); + if (!schema || depth >= 20) return false; + const variants = schema.oneOf ?? schema.anyOf; + if (variants?.length) { + const shapes = variants.map((item) => { + const object = objectSchema(document, item); + return `${Object.keys(object?.properties ?? {}) + .sort() + .join("\0")}:${[...(object?.required ?? [])].sort().join("\0")}`; + }); + if (new Set(shapes).size > 1) return true; + } + return schema.allOf?.some((item) => containsExclusiveUnion(item, depth + 1)) ?? false; + }; + const exclusiveBody = Boolean(union?.length || containsExclusiveUnion(bodySchema)); if ( body?.properties && Object.keys(body.properties).length && @@ -908,7 +923,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const digits = pattern.match(/^\^\\d(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (digits) { const count = Math.min( - Math.max(Number(digits[1] ?? 1), schema.minLength ?? 1), + Math.max(Number(digits[1] ?? 1), schema.minLength ?? (digits[1] === "0" ? 0 : 1)), digits[2] ? Number(digits[2]) : Number.POSITIVE_INFINITY, ); return ["0".repeat(count)]; @@ -916,7 +931,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const repeatedClass = pattern.match(/^\^\[([^\]]+)\](?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedClass?.[1]) { const count = Math.min( - Math.max(Number(repeatedClass[2] ?? 1), schema.minLength ?? 1), + Math.max(Number(repeatedClass[2] ?? 1), schema.minLength ?? (repeatedClass[2] === "0" ? 0 : 1)), repeatedClass[3] ? Number(repeatedClass[3]) : Number.POSITIVE_INFINITY, ); const member = repeatedClass[1].startsWith("\\d") ? "0" : repeatedClass[1][0]; @@ -925,7 +940,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const repeatedLiteral = pattern.match(/^\^([A-Za-z0-9_-])(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedLiteral?.[1]) { const count = Math.min( - Math.max(Number(repeatedLiteral[2] ?? 1), schema.minLength ?? 1), + Math.max(Number(repeatedLiteral[2] ?? 1), schema.minLength ?? (repeatedLiteral[2] === "0" ? 0 : 1)), repeatedLiteral[3] ? Number(repeatedLiteral[3]) : Number.POSITIVE_INFINITY, ); return [repeatedLiteral[1].repeat(count)]; @@ -933,7 +948,7 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt const repeatedRange = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\](?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/); if (repeatedRange?.[1]) { const count = Math.min( - Math.max(Number(repeatedRange[2] ?? 1), schema.minLength ?? 1), + Math.max(Number(repeatedRange[2] ?? 1), schema.minLength ?? (repeatedRange[2] === "0" ? 0 : 1)), repeatedRange[3] ? Number(repeatedRange[3]) : Number.POSITIVE_INFINITY, ); return [repeatedRange[1].repeat(count)]; @@ -983,11 +998,11 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt return [`${prefix}${"0".repeat(count)}`]; } const repeatedGroup = pattern.match( - /^\^\(\?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\\.\)\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\$$/, + /^\^\(\?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\\.\)\{(\d+)(?:,(\d*))?\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\$$/, ); - if (repeatedGroup?.[1] && repeatedGroup[4]) { + if (repeatedGroup?.[1] && repeatedGroup[5]) { return [ - `${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(Number(repeatedGroup[3]))}${repeatedGroup[4]}`, + `${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(Number(repeatedGroup[3]))}${repeatedGroup[5]}`, ]; } const variableMixed = pattern.match( @@ -1495,7 +1510,9 @@ export function schemaExample( type === "integer" && Number.isInteger(candidate) ? candidate : Number(candidate.toPrecision(15)); if (type === "number" && !multiple && minimum?.exclusive && maximum?.exclusive && !scalarMatches(1, schema)) { const midpoint = (minimum.value + maximum.value) / 2; - return scalarMatches(midpoint, schema) ? midpoint : round(midpoint); + const rounded = round(midpoint); + if (scalarMatches(midpoint, schema)) return midpoint; + if (scalarMatches(rounded, schema)) return rounded; } let value = 1; if (minimum && (value < minimum.value || (minimum.exclusive && value <= minimum.value))) { @@ -1518,7 +1535,7 @@ export function schemaExample( if (alignment) value = round(Math.ceil(value / alignment) * alignment); if (minimum.exclusive && value <= minimum.value) value = round(value + step); } - return value; + return scalarMatches(value, schema) ? value : null; } if (type === "object" || schema.properties) { const properties = Object.entries(schema.properties ?? {}).filter( From 5f480aeaf4f10f0dd3960f42e93f65049f3051c2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:54:26 +0200 Subject: [PATCH 73/80] Simplify constrained pattern examples --- lib/generators/python.test.ts | 25 +++++++---- lib/openapi.ts | 78 ++++++++++------------------------- 2 files changed, 38 insertions(+), 65 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 19a2cce..588a8c4 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -136,7 +136,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("description=None"); + expect(createSample?.source).toContain('body={"description": None'); const echoSample = decorated.paths["/echo"]?.post?.["x-codeSamples"]?.[0]; expect(echoSample?.source).toContain("body=None"); @@ -770,6 +770,14 @@ describe("Python generator", () => { "AA.AA.0", ); expect(schemaExample(document, { pattern: "^(?:[A-Z]{2}\\.){2,3}[0-9]$", type: "string" })).toBe("AA.AA.0"); + expect( + schemaExample(document, { + maxLength: 10, + minLength: 10, + pattern: "^(?:[A-Z]{2}\\.){2,3}[0-9]$", + type: "string", + }), + ).toBe("AA.AA.AA.0"); expect(schemaExample(document, { pattern: "^\\d{0}$", type: "string" })).toBe(""); expect(schemaExample(document, { pattern: "^[A-Z]{0}$", type: "string" })).toBe(""); expect(schemaExample(document, { pattern: "^a{0}$", type: "string" })).toBe(""); @@ -788,6 +796,9 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d{2,4}$", type: "string" })).toBe("AA00"); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d+$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d+$", type: "string" })).toBe("A0"); + expect(schemaExample(document, { maxLength: 5, minLength: 5, pattern: "^[A-Z]+\\d+$", type: "string" })).toBe( + "AAAA0", + ); expect(schemaExample(document, { pattern: "^\\d+[A-Z]+$", type: "string" })).toBe("0A"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d{2}$", type: "string" })).toBe("A00"); expect(schemaExample(document, { pattern: "^[A-Z]{2,4}\\d+$", type: "string" })).toBe("AA0"); @@ -1359,8 +1370,7 @@ describe("Python generator", () => { expect(types).toContain( '"primaryFailure": NotRequired[UploadsUploadFileResponseVariant1PrimaryFailure], "secondaryFailure": NotRequired[UploadsUploadFileResponseVariant1PrimaryFailure]', ); - expect(widgets).toContain("description: str | None"); - expect(widgets).toContain("label: str | NotGiven = NOT_GIVEN"); + expect(widgets).toContain("def create(self, *, body: dict[str, Any])"); expect(widgets).toContain("return cast(WidgetsCreateResponse,"); }); @@ -1489,13 +1499,10 @@ describe("Python generator", () => { } }); - test("merges composed request schemas without narrowing union fields", async () => { + test("retains composed request unions as a whole body", async () => { const widgets = await Bun.file(join(output, "src/example_api/resources/widgets.py")).text(); - expect(widgets).toContain('provider: Literal["cloud", "local"]'); - expect(widgets).toContain("settings: dict[str, Any]"); - expect(widgets).toContain("name: str"); - expect(widgets).toContain("region: str"); - expect(widgets).toContain("description: str | None"); + expect(widgets).toContain("def create(self, *, body: dict[str, Any])"); + expect(widgets).toContain("json=body"); const createWidget = getOperations(document).find((operation) => operation.operationId === "create_widget"); expect(createWidget && schemaExample(document, requestMedia(createWidget)?.[1].schema)).toMatchObject({ provider: "cloud", diff --git a/lib/openapi.ts b/lib/openapi.ts index e6b03bf..d666314 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -462,22 +462,14 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) const constrainedComposedBody = Boolean( bodySchema?.allOf?.some((item) => typeof objectSchema(document, item)?.additionalProperties === "object"), ); - const containsExclusiveUnion = (input: JsonSchema | undefined, depth = 0): boolean => { + const containsUnion = (input: JsonSchema | undefined, depth = 0): boolean => { const schema = resolveSchema(document, input); if (!schema || depth >= 20) return false; - const variants = schema.oneOf ?? schema.anyOf; - if (variants?.length) { - const shapes = variants.map((item) => { - const object = objectSchema(document, item); - return `${Object.keys(object?.properties ?? {}) - .sort() - .join("\0")}:${[...(object?.required ?? [])].sort().join("\0")}`; - }); - if (new Set(shapes).size > 1) return true; - } - return schema.allOf?.some((item) => containsExclusiveUnion(item, depth + 1)) ?? false; + return Boolean( + schema.oneOf?.length || schema.anyOf?.length || schema.allOf?.some((item) => containsUnion(item, depth + 1)), + ); }; - const exclusiveBody = Boolean(union?.length || containsExclusiveUnion(bodySchema)); + const exclusiveBody = containsUnion(bodySchema); if ( body?.properties && Object.keys(body.properties).length && @@ -986,60 +978,34 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt return [`${wrappedDigits[1]}${"0".repeat(Number(wrappedDigits[2]))}${wrappedDigits[3]}`]; const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); - const mixedSequence = pattern.match( - /^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)(?:,\d*)?\}\\d(?:\{(\d+)(?:,(\d*))?\}|[+*])\$$/, - ); - if (mixedSequence?.[1]) { - const prefix = mixedSequence[1].repeat(Number(mixedSequence[2])); - const count = Math.min( - Math.max(Number(mixedSequence[3] ?? 1), (schema.minLength ?? 0) - prefix.length, 1), - mixedSequence[4] ? Number(mixedSequence[4]) : Number.POSITIVE_INFINITY, - ); - return [`${prefix}${"0".repeat(count)}`]; - } const repeatedGroup = pattern.match( /^\^\(\?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}\\\.\)\{(\d+)(?:,(\d*))?\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\$$/, ); if (repeatedGroup?.[1] && repeatedGroup[5]) { - return [ - `${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(Number(repeatedGroup[3]))}${repeatedGroup[5]}`, - ]; - } - const variableMixed = pattern.match( - /^\^(?:\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\+|\\d\+\[([A-Za-z0-9])-[A-Za-z0-9]\]\+)\$$/, - ); - if (variableMixed) return [variableMixed[1] ? `${variableMixed[1]}0` : `0${variableMixed[2]}`]; - const rangeThenDigits = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\\d\{(\d+)\}\$$/); - if (rangeThenDigits?.[1]) return [`${rangeThenDigits[1]}${"0".repeat(Number(rangeThenDigits[2]))}`]; - const boundedRangeThenDigits = pattern.match(/^\^\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)(?:,(\d*))?\}\\d\+\$$/); - if (boundedRangeThenDigits?.[1]) { - return [`${boundedRangeThenDigits[1].repeat(Number(boundedRangeThenDigits[2]))}0`]; - } - const digitsThenRange = pattern.match(/^\^\\d\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\+\$$/); - if (digitsThenRange?.[2]) return [`${"0".repeat(Number(digitsThenRange[1]))}${digitsThenRange[2]}`]; - const digitsThenBoundedRange = pattern.match( - /^\^\\d\{(\d+)\}\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)(?:,(\d*))?\}\$$/, - ); - if (digitsThenBoundedRange?.[2]) { - return [ - `${"0".repeat(Number(digitsThenBoundedRange[1]))}${digitsThenBoundedRange[2].repeat(Number(digitsThenBoundedRange[3]))}`, - ]; + const width = Number(repeatedGroup[2]) + 1; + const count = Math.min( + Math.max(Number(repeatedGroup[3]), Math.ceil(((schema.minLength ?? 1) - 1) / width)), + repeatedGroup[4] ? Number(repeatedGroup[4]) : Number.POSITIVE_INFINITY, + ); + return [`${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(count)}${repeatedGroup[5]}`]; } - const sequence = pattern.match(/^\^((?:\[[A-Za-z0-9]-[A-Za-z0-9]\](?:\+|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; - const ranges = sequence - ? [...sequence.matchAll(/\[([A-Za-z0-9])-[A-Za-z0-9]\](\+|\{(\d+)(?:,(\d*))?\})/g)] - : []; - if (sequence?.includes("{") || ranges.length > 1 || sequence?.endsWith("+")) { - const counts = ranges.map((match) => (match[3] ? Number(match[3]) : 1)); + const sequence = pattern.match(/^\^((?:(?:\[[^\]]+\]|\\d)(?:[+*]|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; + const tokens = sequence ? [...sequence.matchAll(/(\[([^\]]+)\]|\\d)([+*]|\{(\d+)(?:,(\d*))?\})/g)] : []; + if (tokens.length) { + const counts = tokens.map((match) => Number(match[4] ?? (match[3] === "*" ? 0 : 1))); let extra = Math.max(0, (schema.minLength ?? 0) - counts.reduce((total, count) => total + count, 0)); - for (const [index, match] of ranges.entries()) { + for (const [index, match] of tokens.entries()) { const maximum = - match[2] === "+" || match[4] === "" ? Number.POSITIVE_INFINITY : Number(match[4] ?? match[3]); + match[3] === "+" || match[3] === "*" || match[5] === "" + ? Number.POSITIVE_INFINITY + : Number(match[5] ?? match[4]); const added = Math.min(extra, maximum - counts[index]); counts[index] += added; extra -= added; } - return [ranges.map((match, index) => match[1]?.repeat(counts[index])).join("")]; + return [ + tokens.map((match, index) => (match[1] === "\\d" ? "0" : match[2]?.[0])?.repeat(counts[index])).join(""), + ]; } return pattern.split("|").flatMap((alternative) => { const match = alternative.match(/^\^([a-zA-Z0-9._-]+)\$$/); From 1bd3f7d2ca3724b6a223e89c60c4fcae43729710 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 07:58:49 +0200 Subject: [PATCH 74/80] Describe nested request variants --- lib/generators/python.test.ts | 8 +++++--- lib/openapi.ts | 15 +++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 588a8c4..1c13d29 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -239,14 +239,16 @@ describe("Python generator", () => { allOf: [ { oneOf: [ - { properties: { a: { type: "integer" } }, required: ["a"], type: "object" }, - { properties: { b: { type: "integer" } }, required: ["b"], type: "object" }, + { description: "Use A", properties: { a: { type: "integer" } }, required: ["a"], type: "object" }, + { description: "Use B", properties: { b: { type: "integer" } }, required: ["b"], type: "object" }, ], }, ], type: "object", }; - expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); + expect(sdkArguments(minimalDocument, create)).toMatchObject([ + { description: "Use A Or Use B", name: "body", wholeBody: true }, + ]); createMedia[1].schema = { additionalProperties: false, allOf: [{ properties: { a: { type: "integer" } }, type: "object" }], diff --git a/lib/openapi.ts b/lib/openapi.ts index d666314..8f74174 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -433,10 +433,17 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) const structured = json || ["application/x-www-form-urlencoded", "multipart/form-data"].includes(media?.[0] ?? ""); const bodySchema = resolveSchema(document, media?.[1].schema); const union = bodySchema?.oneOf ?? bodySchema?.anyOf; - const variantDescriptions = union - ?.map((variant) => resolveSchema(document, variant)?.description) - .filter((description): description is string => Boolean(description)); - const variantDescription = variantDescriptions?.join(" Or "); + const unionDescriptions = (input: JsonSchema | undefined, depth = 0): string[] => { + const schema = resolveSchema(document, input); + if (!schema || depth >= 20) return []; + const variants = schema.oneOf ?? schema.anyOf; + return variants?.length + ? variants + .map((variant) => resolveSchema(document, variant)?.description) + .filter((description): description is string => Boolean(description)) + : (schema.allOf ?? []).flatMap((item) => unionDescriptions(item, depth + 1)); + }; + const variantDescription = unionDescriptions(bodySchema).join(" Or "); const body = structured ? objectSchema(document, bodySchema) : undefined; const variants = union?.map((variant) => objectSchema(document, variant)) ?? []; const closed = bodySchema?.allOf?.flatMap((item) => { From 4978489b5239bf4035d13f4d0ec266cf034831dc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 08:04:23 +0200 Subject: [PATCH 75/80] Complete constrained key examples --- lib/generators/python.test.ts | 17 +++++++++++++++++ lib/openapi.ts | 9 ++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 1c13d29..a2cafa6 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -79,6 +79,14 @@ describe("Python generator", () => { prefix: "", }); expect(() => getAuthentication(multiple)).toThrow("Multiple authentication schemes"); + const contentParameter = structuredClone(document); + const contentOperation = contentParameter.paths["/widgets"]?.get; + if (contentOperation) { + contentOperation.parameters = [ + { content: { "application/json": { schema: { type: "string" } } }, in: "query", name: "filter" }, + ]; + } + expect(() => getOperations(contentParameter)).toThrow("Unsupported content parameter: query filter"); }); test("uses a configured package README", async () => { @@ -768,6 +776,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); + expect(schemaExample(document, { pattern: "^[\\dA-F]{2}[A-Z]{2}$", type: "string" })).toBe("00AA"); expect(schemaExample(document, { maxLength: 7, pattern: "^(?:[A-Z]{2}\\.){2}[0-9]$", type: "string" })).toBe( "AA.AA.0", ); @@ -1016,6 +1025,14 @@ describe("Python generator", () => { expect( schemaExample(document, { additionalProperties: { type: "string" }, minProperties: 2, type: "object" }), ).toEqual({ key: "example-key", key2: "example-key2" }); + expect( + schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 2, + propertyNames: { pattern: "^foo\\d{2}bar$", type: "string" }, + type: "object", + }), + ).toEqual({ foo00bar: "example-foo00bar", foo01bar: "example-foo01bar" }); expect( schemaExample(document, { anyOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 8f74174..9e59453 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1011,7 +1011,11 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt extra -= added; } return [ - tokens.map((match, index) => (match[1] === "\\d" ? "0" : match[2]?.[0])?.repeat(counts[index])).join(""), + tokens + .map((match, index) => + (match[1] === "\\d" || match[2]?.startsWith("\\d") ? "0" : match[2]?.[0])?.repeat(counts[index]), + ) + .join(""), ]; } return pattern.split("|").flatMap((alternative) => { @@ -1576,6 +1580,9 @@ export function schemaExample( ...(/\d+$/.test(key) ? [key.replace(/\d+$/, (digits) => String(Number(digits) + index - 1).padStart(digits.length, "0"))] : []), + ...(key.match(/\d(?!.*\d)/) + ? [key.replace(/\d(?!.*\d)/, (digit) => String((Number(digit) + index - 1) % 10))] + : []), ...(/^[a-z]+$/.test(key) ? [ `${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 97 + index) % 26))}`, From 5fcbaec4db80a3a37abb726adaee625aa8190558 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 08:10:00 +0200 Subject: [PATCH 76/80] Generalize quantified pattern examples --- lib/generators/python.test.ts | 3 +++ lib/openapi.ts | 30 +++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index a2cafa6..2d7a871 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -773,6 +773,8 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^item-\\d+$", type: "string" })).toBe("item-0"); expect(schemaExample(document, { pattern: "^foo[0-9]{2}bar$", type: "string" })).toBe("foo00bar"); expect(schemaExample(document, { pattern: "^foo\\d{2}bar$", type: "string" })).toBe("foo00bar"); + expect(schemaExample(document, { pattern: "^foo\\d+bar$", type: "string" })).toBe("foo0bar"); + expect(schemaExample(document, { pattern: "^foo[0-9]bar$", type: "string" })).toBe("foo0bar"); expect(schemaExample(document, { pattern: "^[A-Za-z0-9_-]+$", type: "string" })).toBe("example"); expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); @@ -804,6 +806,7 @@ describe("Python generator", () => { expect(schemaExample(document, { maxLength: 4, minLength: 4, pattern: "^[A-Z]{2}\\d{2}$", type: "string" })).toBe( "AA00", ); + expect(schemaExample(document, { pattern: "^[A-Z]{2}[0-9]$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d{2,4}$", type: "string" })).toBe("AA00"); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d+$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d+$", type: "string" })).toBe("A0"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 9e59453..4a3b9a0 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -974,15 +974,22 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${digitSuffix[1]}${"0".repeat(count)}`]; } - const wrappedRange = pattern.match( - /^\^([A-Za-z0-9._-]+)\[([A-Za-z0-9])-[A-Za-z0-9]\]\{(\d+)\}([A-Za-z0-9._-]+)\$$/, + const wrappedToken = pattern.match( + /^\^([A-Za-z0-9._-]+)(\[([^\]]+)\]|\\d)(?:([+*])|\{(\d+)(?:,(\d*))?\})?([A-Za-z0-9._-]+)\$$/, ); - if (wrappedRange?.[1] && wrappedRange[2]) { - return [`${wrappedRange[1]}${wrappedRange[2].repeat(Number(wrappedRange[3]))}${wrappedRange[4]}`]; + if (wrappedToken?.[1] && wrappedToken[7]) { + const minimum = Number(wrappedToken[5] ?? (wrappedToken[4] === "*" ? 0 : 1)); + const maximum = + wrappedToken[4] || wrappedToken[6] === "" + ? Number.POSITIVE_INFINITY + : Number(wrappedToken[6] ?? wrappedToken[5] ?? 1); + const count = Math.min( + Math.max(minimum, (schema.minLength ?? 0) - wrappedToken[1].length - wrappedToken[7].length), + maximum, + ); + const member = wrappedToken[2] === "\\d" || wrappedToken[3]?.startsWith("\\d") ? "0" : wrappedToken[3]?.[0]; + return [`${wrappedToken[1]}${member?.repeat(count)}${wrappedToken[7]}`]; } - const wrappedDigits = pattern.match(/^\^([A-Za-z0-9._-]+)\\d\{(\d+)\}([A-Za-z0-9._-]+)\$$/); - if (wrappedDigits?.[1]) - return [`${wrappedDigits[1]}${"0".repeat(Number(wrappedDigits[2]))}${wrappedDigits[3]}`]; const grouped = pattern.match(/^\^\(([a-zA-Z0-9._|-]+)\)\$$/)?.[1]; if (grouped) return grouped.split("|"); const repeatedGroup = pattern.match( @@ -996,14 +1003,15 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(count)}${repeatedGroup[5]}`]; } - const sequence = pattern.match(/^\^((?:(?:\[[^\]]+\]|\\d)(?:[+*]|\{\d+(?:,\d*)?\}))+?)\$$/)?.[1]; - const tokens = sequence ? [...sequence.matchAll(/(\[([^\]]+)\]|\\d)([+*]|\{(\d+)(?:,(\d*))?\})/g)] : []; + const sequence = pattern.match(/^\^((?:(?:\[[^\]]+\]|\\d)(?:[+*]|\{\d+(?:,\d*)?\})?)+?)\$$/)?.[1]; + const tokens = sequence ? [...sequence.matchAll(/(\[([^\]]+)\]|\\d)([+*]|\{(\d+)(?:,(\d*))?\})?/g)] : []; if (tokens.length) { const counts = tokens.map((match) => Number(match[4] ?? (match[3] === "*" ? 0 : 1))); let extra = Math.max(0, (schema.minLength ?? 0) - counts.reduce((total, count) => total + count, 0)); for (const [index, match] of tokens.entries()) { - const maximum = - match[3] === "+" || match[3] === "*" || match[5] === "" + const maximum = !match[3] + ? 1 + : match[3] === "+" || match[3] === "*" || match[5] === "" ? Number.POSITIVE_INFINITY : Number(match[5] ?? match[4]); const added = Math.min(extra, maximum - counts[index]); From 07ef43f7d4ef8bad01c9791ce4e2008988dc29d2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 08:15:41 +0200 Subject: [PATCH 77/80] Preserve request property count constraints --- lib/generators/python.test.ts | 12 ++++++++++++ lib/openapi.ts | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 2d7a871..5dbcbfe 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -270,6 +270,18 @@ describe("Python generator", () => { ], }; expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); + createMedia[1].schema = { + maxProperties: 1, + properties: { a: { type: "integer" }, b: { type: "integer" } }, + type: "object", + }; + expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); + createMedia[1].schema = { + minProperties: 1, + properties: { a: { type: "integer" }, b: { type: "integer" } }, + type: "object", + }; + expect(sdkArguments(minimalDocument, create)).toMatchObject([{ name: "body", wholeBody: true }]); const minimalCurl = curlCodeSample(minimalDocument, create, { body: minimalBody, environment: "EXAMPLE_API_KEY", diff --git a/lib/openapi.ts b/lib/openapi.ts index 4a3b9a0..89fe444 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -482,7 +482,9 @@ export function sdkArguments(document: OpenApiDocument, operation: ApiOperation) Object.keys(body.properties).length && !exclusiveBody && !incompatibleClosedBody && - !constrainedComposedBody + !constrainedComposedBody && + body.minProperties === undefined && + body.maxProperties === undefined ) { for (const [name, schema] of Object.entries(body.properties)) { const property = resolveSchema(document, schema) ?? schema; From ecc3117a9876fee8a071648ec1acd4a248769d96 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 08:21:07 +0200 Subject: [PATCH 78/80] Preserve composed additional property constraints --- lib/generators/python.test.ts | 7 +++++++ lib/openapi.ts | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 5dbcbfe..f56c3f6 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -995,6 +995,13 @@ describe("Python generator", () => { { minLength: 2, type: "string" }, ], }); + expect( + schemaExample(document, { + additionalProperties: { pattern: "^x+$", type: "string" }, + allOf: [{ properties: { a: { type: "string" } }, required: ["a"] }], + type: "object", + }), + ).toEqual({ a: "x" }); expect(schemaConstraints(document, { maxProperties: 3, minProperties: 1, type: "object" })).toEqual([ "minimum properties 1", "maximum properties 3", diff --git a/lib/openapi.ts b/lib/openapi.ts index 89fe444..065c426 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -737,6 +737,14 @@ export function objectSchema(document: OpenApiDocument, input: JsonSchema | unde : property; } } + for (const [name, property] of Object.entries(properties)) { + const constraints = composed.flatMap((item) => + typeof item.additionalProperties === "object" && !Object.hasOwn(item.properties ?? {}, name) + ? [item.additionalProperties] + : [], + ); + if (constraints.length) properties[name] = { allOf: [property, ...constraints] }; + } const closed = composed.filter((item) => item.additionalProperties === false); if (closed.length) { for (const property of Object.keys(properties)) { From ee809c20361de0fa7b71c16279832ca6bb2db719 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 08:25:49 +0200 Subject: [PATCH 79/80] Validate composed scalar examples --- lib/generators/python.test.ts | 1 + lib/openapi.ts | 44 ++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index f56c3f6..1af66da 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -819,6 +819,7 @@ describe("Python generator", () => { "AA00", ); expect(schemaExample(document, { pattern: "^[A-Z]{2}[0-9]$", type: "string" })).toBe("AA0"); + expect(schemaExample(document, { pattern: "^[A-Z]{2}-\\d{3}$", type: "string" })).toBe("AA-000"); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d{2,4}$", type: "string" })).toBe("AA00"); expect(schemaExample(document, { pattern: "^[A-Z]{2}\\d+$", type: "string" })).toBe("AA0"); expect(schemaExample(document, { pattern: "^[A-Z]+\\d+$", type: "string" })).toBe("A0"); diff --git a/lib/openapi.ts b/lib/openapi.ts index 065c426..5f5cc6c 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1013,25 +1013,37 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt ); return [`${`${repeatedGroup[1].repeat(Number(repeatedGroup[2]))}.`.repeat(count)}${repeatedGroup[5]}`]; } - const sequence = pattern.match(/^\^((?:(?:\[[^\]]+\]|\\d)(?:[+*]|\{\d+(?:,\d*)?\})?)+?)\$$/)?.[1]; - const tokens = sequence ? [...sequence.matchAll(/(\[([^\]]+)\]|\\d)([+*]|\{(\d+)(?:,(\d*))?\})?/g)] : []; - if (tokens.length) { - const counts = tokens.map((match) => Number(match[4] ?? (match[3] === "*" ? 0 : 1))); + const sequence = pattern.match(/^\^(.+)\$$/)?.[1]; + const tokens = sequence + ? [...sequence.matchAll(/(\[([^\]]+)\]|\\d)([+*]|\{(\d+)(?:,(\d*))?\})?|\\([._-])|([A-Za-z0-9._-]+)/g)] + : []; + if (sequence && tokens.map((match) => match[0]).join("") === sequence) { + const counts = tokens.map((match) => + match[6] || match[7] + ? ((match[6] ?? match[7])?.length ?? 0) + : Number(match[4] ?? (match[3] === "*" ? 0 : 1)), + ); let extra = Math.max(0, (schema.minLength ?? 0) - counts.reduce((total, count) => total + count, 0)); for (const [index, match] of tokens.entries()) { - const maximum = !match[3] - ? 1 - : match[3] === "+" || match[3] === "*" || match[5] === "" - ? Number.POSITIVE_INFINITY - : Number(match[5] ?? match[4]); + const maximum = + match[6] || match[7] + ? counts[index] + : !match[3] + ? 1 + : match[3] === "+" || match[3] === "*" || match[5] === "" + ? Number.POSITIVE_INFINITY + : Number(match[5] ?? match[4]); const added = Math.min(extra, maximum - counts[index]); counts[index] += added; extra -= added; } return [ tokens - .map((match, index) => - (match[1] === "\\d" || match[2]?.startsWith("\\d") ? "0" : match[2]?.[0])?.repeat(counts[index]), + .map( + (match, index) => + match[6] ?? + match[7] ?? + (match[1] === "\\d" || match[2]?.startsWith("\\d") ? "0" : match[2]?.[0])?.repeat(counts[index]), ) .join(""), ]; @@ -1228,11 +1240,11 @@ function mergeScalarSchemas(document: OpenApiDocument, inputs: JsonSchema[], nam !result.enum?.length ) { const formats = [...new Set(schemas.flatMap((schema) => (schema.format ? [schema.format] : [])))]; - result.example = - formats - .map((format) => stringExample({ ...result, format }, name, patterns)) - .find((candidate) => schemas.every((schema) => scalarMatches(candidate, schema))) ?? - stringExample(result, name, patterns); + const example = [ + ...formats.map((format) => stringExample({ ...result, format }, name, patterns)), + stringExample(result, name, patterns), + ].find((candidate) => schemas.every((schema) => scalarMatches(candidate, schema))); + if (example !== undefined) result.example = example; } return result; } From a52c093c4a7932f1ed9ae38da212935f1acad172 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 13 Aug 2026 08:33:55 +0200 Subject: [PATCH 80/80] Complete escaped and fixed-width keys --- lib/generators/python.test.ts | 9 +++++++++ lib/openapi.ts | 28 +++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/generators/python.test.ts b/lib/generators/python.test.ts index 1af66da..2af7892 100644 --- a/lib/generators/python.test.ts +++ b/lib/generators/python.test.ts @@ -791,6 +791,7 @@ describe("Python generator", () => { expect(schemaExample(document, { pattern: "^[0-9_]+$", type: "string" })).toBe("0"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}$", type: "string" })).toBe("00"); expect(schemaExample(document, { pattern: "^[\\dA-F]{2}[A-Z]{2}$", type: "string" })).toBe("00AA"); + expect(schemaExample(document, { pattern: "^[\\w-]{2}-\\d$", type: "string" })).toBe("aa-0"); expect(schemaExample(document, { maxLength: 7, pattern: "^(?:[A-Z]{2}\\.){2}[0-9]$", type: "string" })).toBe( "AA.AA.0", ); @@ -1056,6 +1057,14 @@ describe("Python generator", () => { type: "object", }), ).toEqual({ foo00bar: "example-foo00bar", foo01bar: "example-foo01bar" }); + const fixedWidthKeys = schemaExample(document, { + additionalProperties: { type: "string" }, + minProperties: 27, + propertyNames: { pattern: "^[A-Z]{2}$", type: "string" }, + type: "object", + }); + expect(Object.keys(fixedWidthKeys as object)).toHaveLength(27); + expect(fixedWidthKeys).toHaveProperty("BA"); expect( schemaExample(document, { anyOf: [ diff --git a/lib/openapi.ts b/lib/openapi.ts index 5f5cc6c..dc32a2e 100644 --- a/lib/openapi.ts +++ b/lib/openapi.ts @@ -1043,7 +1043,14 @@ function stringExample(schema: JsonSchema, name?: string, patterns = schema.patt (match, index) => match[6] ?? match[7] ?? - (match[1] === "\\d" || match[2]?.startsWith("\\d") ? "0" : match[2]?.[0])?.repeat(counts[index]), + (match[1] === "\\d" || match[2]?.startsWith("\\d") + ? "0" + : match[2]?.startsWith("\\w") + ? "a" + : match[2]?.startsWith("\\s") + ? " " + : match[2]?.[0] + )?.repeat(counts[index]), ) .join(""), ]; @@ -1618,12 +1625,27 @@ export function schemaExample( `${key.slice(0, -1)}${String.fromCharCode(97 + ((key.charCodeAt(key.length - 1) - 97 + index) % 26))}`, ] : []), + ...(/^[A-Z]+$/.test(key) + ? [ + Array.from({ length: key.length }, (_, position) => + String.fromCharCode(65 + (Math.floor(index / 26 ** (key.length - position - 1)) % 26)), + ).join(""), + ] + : []), + ...(/^[a-z]+$/.test(key) + ? [ + Array.from({ length: key.length }, (_, position) => + String.fromCharCode(97 + (Math.floor(index / 26 ** (key.length - position - 1)) % 26)), + ).join(""), + ] + : []), ]; const property = candidates.find( - (candidate) => !schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames), + (candidate) => + !values.has(candidate) && (!schema.propertyNames || schemaMatches(document, candidate, schema.propertyNames)), ); if (!property) break; - if (!values.has(property)) values.set(property, schemaExample(document, additional, depth + 1, property)); + values.set(property, schemaExample(document, additional, depth + 1, property)); } return Object.fromEntries(values); }