Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/fix-anyof-with-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": patch
---

Fix anyOf with sibling properties to generate intersection instead of union
Original file line number Diff line number Diff line change
Expand Up @@ -105595,7 +105595,7 @@ export interface operations {
/** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */
readonly identifier: string;
}[];
} | ({
} & (({
/** @enum {unknown} */
readonly status?: "completed";
} & {
Expand All @@ -105605,7 +105605,7 @@ export interface operations {
readonly status?: "queued" | "in_progress";
} & {
readonly [key: string]: unknown;
});
}));
};
};
readonly responses: {
Expand Down Expand Up @@ -113373,7 +113373,7 @@ export interface operations {
*/
readonly path: "/" | "/docs";
};
} | unknown | unknown | unknown | unknown | unknown;
} & (unknown | unknown | unknown | unknown | unknown);
};
};
readonly responses: {
Expand Down Expand Up @@ -113420,7 +113420,7 @@ export interface operations {
*/
readonly path?: "/" | "/docs";
};
} | unknown | unknown) | null;
} & (unknown | unknown)) | null;
};
};
readonly responses: {
Expand Down Expand Up @@ -114769,7 +114769,7 @@ export interface operations {
readonly reviewers?: readonly string[];
/** @description An array of team `slug`s that will be requested. */
readonly team_reviewers?: readonly string[];
} | unknown | unknown;
} & (unknown | unknown);
};
};
readonly responses: {
Expand Down
10 changes: 5 additions & 5 deletions packages/openapi-typescript/examples/github-api-immutable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105595,7 +105595,7 @@ export interface operations {
/** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */
readonly identifier: string;
}[];
} | ({
} & (({
/** @enum {unknown} */
readonly status?: "completed";
} & {
Expand All @@ -105605,7 +105605,7 @@ export interface operations {
readonly status?: "queued" | "in_progress";
} & {
readonly [key: string]: unknown;
});
}));
};
};
readonly responses: {
Expand Down Expand Up @@ -113373,7 +113373,7 @@ export interface operations {
*/
readonly path: "/" | "/docs";
};
} | unknown | unknown | unknown | unknown | unknown;
} & (unknown | unknown | unknown | unknown | unknown);
};
};
readonly responses: {
Expand Down Expand Up @@ -113420,7 +113420,7 @@ export interface operations {
*/
readonly path?: "/" | "/docs";
};
} | unknown | unknown) | null;
} & (unknown | unknown)) | null;
};
};
readonly responses: {
Expand Down Expand Up @@ -114769,7 +114769,7 @@ export interface operations {
readonly reviewers?: readonly string[];
/** @description An array of team `slug`s that will be requested. */
readonly team_reviewers?: readonly string[];
} | unknown | unknown;
} & (unknown | unknown);
};
};
readonly responses: {
Expand Down
16 changes: 8 additions & 8 deletions packages/openapi-typescript/examples/github-api-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25412,7 +25412,7 @@ export interface components {
* @example 1
*/
id: number;
account: (null | (components["schemas"]["simple-user"] | components["schemas"]["enterprise"])) | components["schemas"]["simple-user"] | components["schemas"]["enterprise"];
account: (null | (components["schemas"]["simple-user"] | components["schemas"]["enterprise"])) & (components["schemas"]["simple-user"] | components["schemas"]["enterprise"]);
/**
* @description Describe whether all repositories have been selected or there's a selection involved
* @enum {string}
Expand Down Expand Up @@ -31366,7 +31366,7 @@ export interface components {
href?: string;
} | null;
};
conditions?: (null | (components["schemas"]["repository-ruleset-conditions"] | components["schemas"]["org-ruleset-conditions"])) | components["schemas"]["repository-ruleset-conditions"] | components["schemas"]["org-ruleset-conditions"];
conditions?: (null | (components["schemas"]["repository-ruleset-conditions"] | components["schemas"]["org-ruleset-conditions"])) & (components["schemas"]["repository-ruleset-conditions"] | components["schemas"]["org-ruleset-conditions"]);
rules?: components["schemas"]["repository-rule"][];
/** Format: date-time */
created_at?: string;
Expand Down Expand Up @@ -35615,7 +35615,7 @@ export interface components {
* @description User-defined metadata to store domain-specific information limited to 8 keys with scalar values.
*/
metadata: {
[key: string]: (null | (string | number | boolean) | (number | string | boolean) | (boolean | string | number)) | string | number | boolean;
[key: string]: (null | (string & (string | number | boolean)) | (number & (string | number | boolean)) | (boolean & (string | number | boolean))) & (string | number | boolean);
};
dependency: {
/**
Expand Down Expand Up @@ -109814,7 +109814,7 @@ export interface operations {
/** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */
identifier: string;
}[];
} | ({
} & (({
/** @enum {unknown} */
status?: "completed";
} & {
Expand All @@ -109824,7 +109824,7 @@ export interface operations {
status?: "queued" | "in_progress";
} & {
[key: string]: unknown;
});
}));
};
};
responses: {
Expand Down Expand Up @@ -117592,7 +117592,7 @@ export interface operations {
*/
path: "/" | "/docs";
};
} | unknown | unknown | unknown | unknown | unknown;
} & (unknown | unknown | unknown | unknown | unknown);
};
};
responses: {
Expand Down Expand Up @@ -117639,7 +117639,7 @@ export interface operations {
*/
path?: "/" | "/docs";
};
} | unknown | unknown) | null) | unknown | unknown;
} & (unknown | unknown)) | null) & (unknown | unknown);
};
};
responses: {
Expand Down Expand Up @@ -118988,7 +118988,7 @@ export interface operations {
reviewers?: string[];
/** @description An array of team `slug`s that will be requested. */
team_reviewers?: string[];
} | unknown | unknown;
} & (unknown | unknown);
};
};
responses: {
Expand Down
10 changes: 5 additions & 5 deletions packages/openapi-typescript/examples/github-api-required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105595,7 +105595,7 @@ export interface operations {
/** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */
identifier: string;
}[];
} | ({
} & (({
/** @enum {unknown} */
status?: "completed";
} & {
Expand All @@ -105605,7 +105605,7 @@ export interface operations {
status: "queued" | "in_progress";
} & {
[key: string]: unknown;
});
}));
};
};
responses: {
Expand Down Expand Up @@ -113373,7 +113373,7 @@ export interface operations {
*/
path: "/" | "/docs";
};
} | unknown | unknown | unknown | unknown | unknown;
} & (unknown | unknown | unknown | unknown | unknown);
};
};
responses: {
Expand Down Expand Up @@ -113420,7 +113420,7 @@ export interface operations {
*/
path?: "/" | "/docs";
};
} | unknown | unknown) | null;
} & (unknown | unknown)) | null;
};
};
responses: {
Expand Down Expand Up @@ -114769,7 +114769,7 @@ export interface operations {
reviewers: string[];
/** @description An array of team `slug`s that will be requested. */
team_reviewers: string[];
} | unknown | unknown;
} & (unknown | unknown);
};
};
responses: {
Expand Down
10 changes: 5 additions & 5 deletions packages/openapi-typescript/examples/github-api-root-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106622,7 +106622,7 @@ export interface operations {
/** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */
identifier: string;
}[];
} | ({
} & (({
/** @enum {unknown} */
status?: "completed";
} & {
Expand All @@ -106632,7 +106632,7 @@ export interface operations {
status?: "queued" | "in_progress";
} & {
[key: string]: unknown;
});
}));
};
};
responses: {
Expand Down Expand Up @@ -114400,7 +114400,7 @@ export interface operations {
*/
path: "/" | "/docs";
};
} | unknown | unknown | unknown | unknown | unknown;
} & (unknown | unknown | unknown | unknown | unknown);
};
};
responses: {
Expand Down Expand Up @@ -114447,7 +114447,7 @@ export interface operations {
*/
path?: "/" | "/docs";
};
} | unknown | unknown) | null;
} & (unknown | unknown)) | null;
};
};
responses: {
Expand Down Expand Up @@ -115796,7 +115796,7 @@ export interface operations {
reviewers?: string[];
/** @description An array of team `slug`s that will be requested. */
team_reviewers?: string[];
} | unknown | unknown;
} & (unknown | unknown);
};
};
responses: {
Expand Down
10 changes: 5 additions & 5 deletions packages/openapi-typescript/examples/github-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105595,7 +105595,7 @@ export interface operations {
/** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */
identifier: string;
}[];
} | ({
} & (({
/** @enum {unknown} */
status?: "completed";
} & {
Expand All @@ -105605,7 +105605,7 @@ export interface operations {
status?: "queued" | "in_progress";
} & {
[key: string]: unknown;
});
}));
};
};
responses: {
Expand Down Expand Up @@ -113373,7 +113373,7 @@ export interface operations {
*/
path: "/" | "/docs";
};
} | unknown | unknown | unknown | unknown | unknown;
} & (unknown | unknown | unknown | unknown | unknown);
};
};
responses: {
Expand Down Expand Up @@ -113420,7 +113420,7 @@ export interface operations {
*/
path?: "/" | "/docs";
};
} | unknown | unknown) | null;
} & (unknown | unknown)) | null;
};
};
responses: {
Expand Down Expand Up @@ -114769,7 +114769,7 @@ export interface operations {
reviewers?: string[];
/** @description An array of team `slug`s that will be requested. */
team_reviewers?: string[];
} | unknown | unknown;
} & (unknown | unknown);
};
};
responses: {
Expand Down
15 changes: 11 additions & 4 deletions packages/openapi-typescript/src/transform/schema-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,17 @@ export function transformSchemaObjectWithComposition(
finalType = tsIntersection([...(coreObjectType ? [coreObjectType] : []), ...(allOf ? [allOf] : [])]);
}
// anyOf: union
// (note: this may seem counterintuitive, but as TypeScript’s unions are not true XORs, they mimic behavior closer to anyOf than oneOf)
// (note: this may seem counterintuitive, but as TypeScript's unions are not true XORs, they mimic behavior closer to anyOf than oneOf)
// When there are sibling properties alongside anyOf, they should be intersected with the union (issue #2380)
const anyOfType = collectUnionCompositions(schemaObject.anyOf ?? [], "anyOf");
if (anyOfType.length) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]);
if (finalType) {
// If there are sibling properties, intersect them with the anyOf union
finalType = tsIntersection([finalType, tsUnion(anyOfType)]);
} else {
// If no sibling properties, just use the union
finalType = tsUnion(anyOfType);
}
}
// oneOf: union (within intersection with other types, if any)
const oneOfType = collectUnionCompositions(
Expand Down Expand Up @@ -472,7 +479,7 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor
t === "null" || t === null
? NULL
: transformSchemaObject(
{ ...schemaObject, type: t, oneOf: undefined } as SchemaObject, // dont stack oneOf transforms
{ ...schemaObject, type: t, oneOf: undefined } as SchemaObject, // don't stack oneOf transforms
options,
),
);
Expand Down Expand Up @@ -558,7 +565,7 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor
options.ctx.defaultNonNullable &&
!options.path?.includes("parameters") &&
!options.path?.includes("requestBody") &&
!options.path?.includes("requestBodies")) // cant be required, even with defaults
!options.path?.includes("requestBodies")) // can't be required, even with defaults
? undefined
: QUESTION_TOKEN;
let type = $ref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,40 @@ describe("composition", () => {
// options: DEFAULT_OPTIONS
},
],
[
"anyOf > with sibling properties (issue #2380)",
{
given: {
anyOf: [
{
type: "object",
properties: { type: { type: "string", enum: ["email"] }, address: { type: "string" } },
required: ["type", "address"],
},
{
type: "object",
properties: { type: { type: "string", enum: ["phone"] }, number: { type: "string" } },
required: ["type", "number"],
},
],
properties: {
name: { type: "string" },
},
},
want: `{
name?: string;
} & ({
/** @enum {string} */
type: "email";
address: string;
} | {
/** @enum {string} */
type: "phone";
number: string;
})`,
// options: DEFAULT_OPTIONS
},
],
];

for (const [testName, { given, want, options = DEFAULT_OPTIONS, ci }] of tests) {
Expand Down