diff --git a/.changeset/eleven-shoes-mate.md b/.changeset/eleven-shoes-mate.md new file mode 100644 index 000000000..bb1468886 --- /dev/null +++ b/.changeset/eleven-shoes-mate.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": minor +--- + +Add support for patternProperties diff --git a/packages/openapi-typescript/src/transform/schema-object.ts b/packages/openapi-typescript/src/transform/schema-object.ts index 7219664df..7897f9da2 100644 --- a/packages/openapi-typescript/src/transform/schema-object.ts +++ b/packages/openapi-typescript/src/transform/schema-object.ts @@ -448,6 +448,7 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor if ( ("properties" in schemaObject && schemaObject.properties && Object.keys(schemaObject.properties).length) || ("additionalProperties" in schemaObject && schemaObject.additionalProperties) || + ("patternProperties" in schemaObject && schemaObject.patternProperties) || ("$defs" in schemaObject && schemaObject.$defs) ) { // properties @@ -547,13 +548,22 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor ); } - // additionalProperties - if (schemaObject.additionalProperties || options.ctx.additionalProperties) { + // additionalProperties / patternProperties + if (schemaObject.additionalProperties || options.ctx.additionalProperties || schemaObject.patternProperties) { const hasExplicitAdditionalProperties = typeof schemaObject.additionalProperties === "object" && Object.keys(schemaObject.additionalProperties).length; - const addlType = hasExplicitAdditionalProperties - ? transformSchemaObject(schemaObject.additionalProperties as SchemaObject, options) - : UNKNOWN; + const hasExplicitPatternProperties = + typeof schemaObject.patternProperties === "object" && Object.keys(schemaObject.patternProperties).length; + const addlTypes = []; + if (hasExplicitAdditionalProperties) { + addlTypes.push(transformSchemaObject(schemaObject.additionalProperties as SchemaObject, options)); + } + if (hasExplicitPatternProperties) { + for (const [_, v] of getEntries(schemaObject.patternProperties ?? {}, options.ctx)) { + addlTypes.push(transformSchemaObject(v, options)); + } + } + const addlType = addlTypes.length === 0 ? UNKNOWN : tsUnion(addlTypes); return tsIntersection([ ...(coreObjectType.length ? [ts.factory.createTypeLiteralNode(coreObjectType)] : []), ts.factory.createTypeLiteralNode([ diff --git a/packages/openapi-typescript/src/types.ts b/packages/openapi-typescript/src/types.ts index 75d8f8c07..6cb985e71 100644 --- a/packages/openapi-typescript/src/types.ts +++ b/packages/openapi-typescript/src/types.ts @@ -502,6 +502,7 @@ export interface ObjectSubtype { type: "object" | ["object", "null"]; properties?: { [name: string]: SchemaObject | ReferenceObject }; additionalProperties?: boolean | Record | SchemaObject | ReferenceObject; + patternProperties?: Record; required?: string[]; allOf?: (SchemaObject | ReferenceObject)[]; anyOf?: (SchemaObject | ReferenceObject)[]; diff --git a/packages/openapi-typescript/test/transform/schema-object/object.test.ts b/packages/openapi-typescript/test/transform/schema-object/object.test.ts index 062f734a5..4aa275e80 100644 --- a/packages/openapi-typescript/test/transform/schema-object/object.test.ts +++ b/packages/openapi-typescript/test/transform/schema-object/object.test.ts @@ -99,6 +99,55 @@ describe("transformSchemaObject > object", () => { // options: DEFAULT_OPTIONS, }, ], + [ + "patternProperties > empty object", + { + given: { type: "object", patternProperties: {} }, + want: `{ + [key: string]: unknown; +}`, + }, + ], + [ + "patternProperties > basic", + { + given: { type: "object", patternProperties: { "^a": { type: "string" } } }, + want: `{ + [key: string]: string; +}`, + }, + ], + [ + "patternProperties > enum", + { + given: { type: "object", patternProperties: { "^a": { type: "string", enum: ["a", "b", "c"] } } }, + want: `{ + [key: string]: "a" | "b" | "c"; +}`, + }, + ], + [ + "patternProperties > multiple patterns", + { + given: { type: "object", patternProperties: { "^a": { type: "string" }, "^b": { type: "number" } } }, + want: `{ + [key: string]: string | number; +}`, + }, + ], + [ + "patternProperties > additional and patterns", + { + given: { + type: "object", + additionalProperties: { type: "number" }, + patternProperties: { "^a": { type: "string" } }, + }, + want: `{ + [key: string]: number | string; +}`, + }, + ], [ "nullable", {