Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-shoes-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": minor
---

Add support for patternProperties
33 changes: 28 additions & 5 deletions packages/openapi-typescript/src/transform/schema-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,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
Expand Down Expand Up @@ -542,13 +543,35 @@ 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 hasImplicitAdditionalProperties =
schemaObject.additionalProperties === true ||
(typeof schemaObject.additionalProperties === "object" &&
Object.keys(schemaObject.additionalProperties).length === 0);
const hasExplicitPatternProperties =
typeof schemaObject.patternProperties === "object" && Object.keys(schemaObject.patternProperties).length;
const addlTypes = [];
if (hasExplicitAdditionalProperties) {
addlTypes.push(transformSchemaObject(schemaObject.additionalProperties as SchemaObject, options));
}
if (hasImplicitAdditionalProperties || (!schemaObject.additionalProperties && options.ctx.additionalProperties)) {
addlTypes.push(UNKNOWN);
}
if (hasExplicitPatternProperties) {
for (const [_, v] of getEntries(schemaObject.patternProperties ?? {}, options.ctx)) {
addlTypes.push(transformSchemaObject(v, options));
}
}

if (addlTypes.length === 0) {
return;
}

const addlType = tsUnion(addlTypes);

return tsIntersection([
...(coreObjectType.length ? [ts.factory.createTypeLiteralNode(coreObjectType)] : []),
ts.factory.createTypeLiteralNode([
Expand Down
1 change: 1 addition & 0 deletions packages/openapi-typescript/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ export interface ObjectSubtype {
type: "object" | ["object", "null"];
properties?: { [name: string]: SchemaObject | ReferenceObject };
additionalProperties?: boolean | Record<string, never> | SchemaObject | ReferenceObject;
patternProperties?: Record<string, SchemaObject | ReferenceObject>;
required?: string[];
allOf?: (SchemaObject | ReferenceObject)[];
anyOf?: (SchemaObject | ReferenceObject)[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,95 @@ describe("transformSchemaObject > object", () => {
// options: DEFAULT_OPTIONS,
},
],
[
"patternProperties > empty object",
{
given: { type: "object", patternProperties: {} },
want: "Record<string, never>",
},
],
[
"patternProperties > empty object with options.additionalProperties=true",
{
given: { type: "object", patternProperties: {} },
want: `{
[key: string]: unknown;
}`,
options: {
...DEFAULT_OPTIONS,
ctx: { ...DEFAULT_CTX, additionalProperties: true },
},
},
],
[
"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=true and patterns",
{
given: {
type: "object",
additionalProperties: true,
patternProperties: { "^a": { type: "string" } },
},
want: `{
[key: string]: unknown | string;
}`,
},
],
[
"patternProperties > additional and patterns",
{
given: {
type: "object",
additionalProperties: { type: "number" },
patternProperties: { "^a": { type: "string" } },
},
want: `{
[key: string]: number | string;
}`,
},
],
[
"patternProperties > patterns with options.additionalProperties=true",
{
given: {
type: "object",
patternProperties: { "^a": { type: "string" } },
},
want: `{
[key: string]: unknown | string;
}`,
options: {
...DEFAULT_OPTIONS,
ctx: { ...DEFAULT_CTX, additionalProperties: true },
},
},
],
[
"nullable",
{
Expand Down
Loading