|
1 |
| -import type { GlobalContext, ReferenceObject, SchemaObject } from "../types.js"; |
| 1 | +import type { DiscriminatorObject, GlobalContext, ReferenceObject, SchemaObject } from "../types.js"; |
2 | 2 | import { escObjKey, escStr, getEntries, getSchemaObjectComment, indent, parseRef, tsArrayOf, tsIntersectionOf, tsOmit, tsOneOf, tsOptionalProperty, tsReadonly, tsTupleOf, tsUnionOf, tsWithRequired } from "../utils.js";
|
3 | 3 | import transformSchemaObjectMap from "./schema-object-map.js";
|
4 | 4 |
|
@@ -162,21 +162,24 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere
|
162 | 162 | // core type: properties + additionalProperties
|
163 | 163 | const coreType: string[] = [];
|
164 | 164 |
|
165 |
| - // discriminators |
| 165 | + // discriminators: explicit mapping on schema object |
166 | 166 | for (const k of ["oneOf", "allOf", "anyOf"] as ("oneOf" | "allOf" | "anyOf")[]) {
|
167 | 167 | if (!(schemaObject as any)[k]) continue;
|
168 |
| - const discriminatorRef: ReferenceObject | undefined = (schemaObject as any)[k].find((t: SchemaObject | ReferenceObject) => "$ref" in t && ctx.discriminators[t.$ref]); |
169 |
| - if (discriminatorRef) { |
170 |
| - const discriminator = ctx.discriminators[discriminatorRef.$ref]; |
171 |
| - // get the inferred propertyName value from the last section of the path (as the spec suggests to do) |
172 |
| - let value = parseRef(path).path.pop()!; |
173 |
| - // if mapping, and there’s a match, use this rather than the inferred name |
174 |
| - if (discriminator.mapping) { |
175 |
| - // Mapping value can either be a fully-qualified ref (#/components/schemas/XYZ) or a schema name (XYZ) |
176 |
| - const matchedValue = Object.entries(discriminator.mapping).find(([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).path.pop() === value)); |
177 |
| - if (matchedValue) value = matchedValue[0]; // why was this designed backwards!? |
178 |
| - } |
179 |
| - coreType.unshift(indent(`${escObjKey(discriminator.propertyName)}: ${escStr(value)};`, indentLv + 1)); |
| 168 | + const discriminatorRef: ReferenceObject | undefined = (schemaObject as any)[k].find( |
| 169 | + (t: SchemaObject | ReferenceObject) => |
| 170 | + "$ref" in t && |
| 171 | + (ctx.discriminators[t.$ref] || // explicit allOf from this node |
| 172 | + Object.values(ctx.discriminators).find((d) => d.oneOf?.includes(path))), // implicit oneOf from parent |
| 173 | + ); |
| 174 | + if (discriminatorRef && ctx.discriminators[discriminatorRef.$ref]) { |
| 175 | + coreType.unshift(indent(getDiscriminatorPropertyName(path, ctx.discriminators[discriminatorRef.$ref]), indentLv + 1)); |
| 176 | + break; |
| 177 | + } |
| 178 | + } |
| 179 | + // discriminators: implicit mapping from parent |
| 180 | + for (const d of Object.values(ctx.discriminators)) { |
| 181 | + if (d.oneOf?.includes(path)) { |
| 182 | + coreType.unshift(indent(getDiscriminatorPropertyName(path, d), indentLv + 1)); |
180 | 183 | break;
|
181 | 184 | }
|
182 | 185 | }
|
@@ -236,7 +239,7 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere
|
236 | 239 | const output: string[] = [];
|
237 | 240 | for (const item of items) {
|
238 | 241 | const itemType = transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } });
|
239 |
| - if ("$ref" in item && ctx.discriminators[item.$ref]?.mapping) { |
| 242 | + if ("$ref" in item && ctx.discriminators[item.$ref]) { |
240 | 243 | output.push(tsOmit(itemType, [ctx.discriminators[item.$ref].propertyName]));
|
241 | 244 | continue;
|
242 | 245 | }
|
@@ -274,3 +277,15 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere
|
274 | 277 | // if no type could be generated, fall back to “empty object” type
|
275 | 278 | return ctx.emptyObjectsUnknown ? "Record<string, unknown>" : "Record<string, never>";
|
276 | 279 | }
|
| 280 | + |
| 281 | +export function getDiscriminatorPropertyName(path: string, discriminator: DiscriminatorObject): string { |
| 282 | + // get the inferred propertyName value from the last section of the path (as the spec suggests to do) |
| 283 | + let value = parseRef(path).path.pop()!; |
| 284 | + // if mapping, and there’s a match, use this rather than the inferred name |
| 285 | + if (discriminator.mapping) { |
| 286 | + // Mapping value can either be a fully-qualified ref (#/components/schemas/XYZ) or a schema name (XYZ) |
| 287 | + const matchedValue = Object.entries(discriminator.mapping).find(([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).path.pop() === value)); |
| 288 | + if (matchedValue) value = matchedValue[0]; // why was this designed backwards!? |
| 289 | + } |
| 290 | + return `${escObjKey(discriminator.propertyName)}: ${escStr(value)};`; |
| 291 | +} |
0 commit comments