Skip to content

Commit d417d8b

Browse files
committed
Refactor global context to a single property for all discriminator values
1 parent b084472 commit d417d8b

File tree

6 files changed

+78
-62
lines changed

6 files changed

+78
-62
lines changed

packages/openapi-typescript/src/index.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,11 @@ export default async function openapiTS(
6767
silent: options.silent ?? false,
6868
});
6969

70-
const { discriminators, discriminatorRefsHandled } = scanDiscriminators(
71-
schema,
72-
options,
73-
);
74-
7570
const ctx: GlobalContext = {
7671
additionalProperties: options.additionalProperties ?? false,
7772
alphabetize: options.alphabetize ?? false,
7873
defaultNonNullable: options.defaultNonNullable ?? true,
79-
discriminators,
80-
discriminatorRefsHandled,
74+
discriminators: scanDiscriminators(schema, options),
8175
emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
8276
enum: options.enum ?? false,
8377
excludeDeprecated: options.excludeDeprecated ?? false,

packages/openapi-typescript/src/lib/utils.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,11 @@ export function scanDiscriminators(
184184
schema: OpenAPI3,
185185
options: OpenAPITSOptions,
186186
) {
187-
const discriminators: Record<string, DiscriminatorObject> = {};
188-
// discriminator objects which we have successfully handled to infer the discriminator enum value
189-
const discriminatorRefsHandled: string[] = [];
187+
// all discriminator objects found in the schema
188+
const objects: Record<string, DiscriminatorObject> = {};
189+
190+
// refs of all mapped schema objects we have successfully handled to infer the discriminator enum value
191+
const refsHandled: string[] = [];
190192

191193
// perform 2 passes: first, collect all discriminator definitions and handle oneOf and mappings
192194
walk(schema, (obj, path) => {
@@ -198,7 +200,7 @@ export function scanDiscriminators(
198200
// add to discriminators object for later usage
199201
const ref = createRef(path);
200202

201-
discriminators[ref] = discriminator;
203+
objects[ref] = discriminator;
202204

203205
// if a mapping is available we will help Typescript to infer properties by adding the discriminator enum with its single mapped value to each schema
204206
// we only handle the mapping in advance for discriminator + oneOf compositions right now
@@ -231,7 +233,7 @@ export function scanDiscriminators(
231233
}
232234

233235
for (const [mappedRef, mappedValue] of Object.entries(mapping)) {
234-
if (discriminatorRefsHandled.includes(mappedRef)) {
236+
if (refsHandled.includes(mappedRef)) {
235237
continue;
236238
}
237239

@@ -248,7 +250,7 @@ export function scanDiscriminators(
248250
},
249251
});
250252

251-
discriminatorRefsHandled.push(mappedRef);
253+
refsHandled.push(mappedRef);
252254
} else if (
253255
typeof resolvedSchema === "object" &&
254256
"type" in resolvedSchema &&
@@ -270,7 +272,7 @@ export function scanDiscriminators(
270272
resolvedSchema.properties[discriminator.propertyName] =
271273
createDiscriminatorEnum(mappedValue);
272274

273-
discriminatorRefsHandled.push(mappedRef);
275+
refsHandled.push(mappedRef);
274276
} else {
275277
warn(
276278
`Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${mappedRef} => ${mappedValue} (Discriminator: ${ref})`,
@@ -289,20 +291,20 @@ export function scanDiscriminators(
289291
if (obj && Array.isArray(obj[key])) {
290292
for (const item of (obj as any)[key]) {
291293
if ("$ref" in item) {
292-
if (discriminators[item.$ref]) {
293-
discriminators[createRef(path)] = {
294-
...discriminators[item.$ref],
294+
if (objects[item.$ref]) {
295+
objects[createRef(path)] = {
296+
...objects[item.$ref],
295297
};
296298
}
297299
} else if (item.discriminator?.propertyName) {
298-
discriminators[createRef(path)] = { ...item.discriminator };
300+
objects[createRef(path)] = { ...item.discriminator };
299301
}
300302
}
301303
}
302304
}
303305
});
304306

305-
return { discriminators, discriminatorRefsHandled };
307+
return { objects, refsHandled };
306308
}
307309

308310
/** Walk through any JSON-serializable (i.e. non-circular) object */

packages/openapi-typescript/src/transform/schema-object.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export function transformSchemaObjectWithComposition(
159159
) {
160160
// don’t try and make keys required if we have already handled the item (discriminator property was already added as required)
161161
// or the $ref doesn’t have them
162-
if (!options.ctx.discriminatorRefsHandled.includes(item.$ref)) {
162+
if (!options.ctx.discriminators.refsHandled.includes(item.$ref)) {
163163
const validRequired = (required ?? []).filter(
164164
(key) => !!resolved.properties![key],
165165
);
@@ -185,7 +185,7 @@ export function transformSchemaObjectWithComposition(
185185
);
186186
}
187187
const discriminator =
188-
("$ref" in item && options.ctx.discriminators[item.$ref]) ||
188+
("$ref" in item && options.ctx.discriminators.objects[item.$ref]) ||
189189
(item as any).discriminator; // eslint-disable-line @typescript-eslint/no-explicit-any
190190
if (discriminator) {
191191
output.push(tsOmit(itemType, [discriminator.propertyName]));
@@ -417,7 +417,8 @@ function transformSchemaObjectCore(
417417
// ctx.discriminators. But stop objects from referencing their own
418418
// discriminator meant for children (!schemaObject.discriminator)
419419
const discriminator =
420-
!schemaObject.discriminator && options.ctx.discriminators[options.path!];
420+
!schemaObject.discriminator &&
421+
options.ctx.discriminators.objects[options.path!];
421422
if (discriminator) {
422423
coreObjectType.unshift(
423424
createDiscriminatorProperty(discriminator, {

packages/openapi-typescript/src/types.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -686,8 +686,10 @@ export interface GlobalContext {
686686
additionalProperties: boolean;
687687
alphabetize: boolean;
688688
defaultNonNullable: boolean;
689-
discriminators: Record<string, DiscriminatorObject>;
690-
discriminatorRefsHandled: string[];
689+
discriminators: {
690+
objects: Record<string, DiscriminatorObject>;
691+
refsHandled: string[];
692+
};
691693
emptyObjectsUnknown: boolean;
692694
enum: boolean;
693695
excludeDeprecated: boolean;

packages/openapi-typescript/test/test-helpers.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ export const DEFAULT_CTX: GlobalContext = {
1010
alphabetize: false,
1111
arrayLength: false,
1212
defaultNonNullable: true,
13-
discriminators: {},
14-
discriminatorRefsHandled: [],
13+
discriminators: {
14+
objects: {},
15+
refsHandled: [],
16+
},
1517
emptyObjectsUnknown: false,
1618
enum: false,
1719
excludeDeprecated: false,

packages/openapi-typescript/test/transform/schema-object/composition.test.ts

+51-36
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,21 @@ describe("composition", () => {
219219
ctx: {
220220
...DEFAULT_OPTIONS.ctx,
221221
discriminators: {
222-
[DEFAULT_OPTIONS.path]: {
223-
propertyName: "operation",
224-
mapping: {
225-
test: DEFAULT_OPTIONS.path,
222+
objects: {
223+
[DEFAULT_OPTIONS.path]: {
224+
propertyName: "operation",
225+
mapping: {
226+
test: DEFAULT_OPTIONS.path,
227+
},
226228
},
227-
},
228-
"#/components/schemas/parent": {
229-
propertyName: "operation",
230-
mapping: {
231-
test: DEFAULT_OPTIONS.path,
229+
"#/components/schemas/parent": {
230+
propertyName: "operation",
231+
mapping: {
232+
test: DEFAULT_OPTIONS.path,
233+
},
232234
},
233235
},
236+
refsHandled: [],
234237
},
235238
resolve($ref) {
236239
switch ($ref) {
@@ -274,15 +277,18 @@ describe("composition", () => {
274277
ctx: {
275278
...DEFAULT_OPTIONS.ctx,
276279
discriminators: {
277-
"#/components/schemas/Pet": {
278-
propertyName: "petType",
279-
},
280-
"#/components/schemas/Cat": {
281-
propertyName: "petType",
282-
},
283-
"#/components/schemas/Dog": {
284-
propertyName: "petType",
280+
objects: {
281+
"#/components/schemas/Pet": {
282+
propertyName: "petType",
283+
},
284+
"#/components/schemas/Cat": {
285+
propertyName: "petType",
286+
},
287+
"#/components/schemas/Dog": {
288+
propertyName: "petType",
289+
},
285290
},
291+
refsHandled: [],
286292
},
287293
resolve($ref) {
288294
switch ($ref) {
@@ -315,12 +321,15 @@ describe("composition", () => {
315321
ctx: {
316322
...DEFAULT_OPTIONS.ctx,
317323
discriminators: {
318-
[DEFAULT_OPTIONS.path]: {
319-
propertyName: "operation",
320-
},
321-
"#/components/schemas/parent": {
322-
propertyName: "operation",
324+
objects: {
325+
[DEFAULT_OPTIONS.path]: {
326+
propertyName: "operation",
327+
},
328+
"#/components/schemas/parent": {
329+
propertyName: "operation",
330+
},
323331
},
332+
refsHandled: [],
324333
},
325334
resolve($ref) {
326335
switch ($ref) {
@@ -356,18 +365,21 @@ describe("composition", () => {
356365
ctx: {
357366
...DEFAULT_OPTIONS.ctx,
358367
discriminators: {
359-
"#/components/schemas/schema-object": {
360-
propertyName: "@type",
361-
mapping: {
362-
test: DEFAULT_OPTIONS.path,
368+
objects: {
369+
"#/components/schemas/schema-object": {
370+
propertyName: "@type",
371+
mapping: {
372+
test: DEFAULT_OPTIONS.path,
373+
},
363374
},
364-
},
365-
"#/components/schemas/parent": {
366-
propertyName: "@type",
367-
mapping: {
368-
test: DEFAULT_OPTIONS.path,
375+
"#/components/schemas/parent": {
376+
propertyName: "@type",
377+
mapping: {
378+
test: DEFAULT_OPTIONS.path,
379+
},
369380
},
370381
},
382+
refsHandled: [],
371383
},
372384
resolve($ref) {
373385
switch ($ref) {
@@ -408,12 +420,15 @@ describe("composition", () => {
408420
ctx: {
409421
...DEFAULT_OPTIONS.ctx,
410422
discriminators: {
411-
"#/components/schemas/Pet": {
412-
propertyName: "_petType",
413-
},
414-
"#/components/schemas/Dog": {
415-
propertyName: "_petType",
423+
objects: {
424+
"#/components/schemas/Pet": {
425+
propertyName: "_petType",
426+
},
427+
"#/components/schemas/Dog": {
428+
propertyName: "_petType",
429+
},
416430
},
431+
refsHandled: [],
417432
},
418433
resolve($ref) {
419434
switch ($ref) {

0 commit comments

Comments
 (0)