fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix discriminator inheritance to use Omit for proper type narrowing

- Remove union of child discriminator values from intermediate types
- Each type now gets only its direct discriminator value
- Use Omit<> to remove parent discriminator properties and avoid conflicts
- Enables proper TypeScript type narrowing with discriminated unions

Example: CarDto.$type is now 'Car' instead of 'Car' | 'Volvo'
VolvoDto uses Omit<CarDto, '$type'> & { $type: 'Volvo' }

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>

+110 -38
+5
packages/openapi-ts/src/ir/types.d.ts
··· 183 183 */ 184 184 logicalOperator?: 'and' | 'or'; 185 185 /** 186 + * When used with `$ref` or `symbolRef`, specifies properties to omit from the referenced schema. 187 + * Useful for handling discriminator property conflicts in allOf compositions. 188 + */ 189 + omit?: ReadonlyArray<string>; 190 + /** 186 191 * When type is `object`, `patternProperties` can be used to define a schema 187 192 * for properties that match a specific regex pattern. 188 193 */
+43 -18
packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts
··· 80 80 }; 81 81 82 82 /** 83 - * Gets all discriminator values for a schema and its children in the inheritance hierarchy. 84 - * For intermediate schemas (those that are extended by others), returns a union of all values. 83 + * Gets the discriminator value for a schema. 84 + * Returns only the schema's own discriminator value, not child values. 85 85 */ 86 86 const getAllDiscriminatorValues = ({ 87 - context, 88 87 discriminator, 89 88 schemaRef, 90 89 }: { 91 - context: Context; 92 90 discriminator: NonNullable<SchemaObject['discriminator']>; 93 91 schemaRef: string; 94 92 }): Array<string> => { ··· 101 99 if (mappedSchemaRef === schemaRef) { 102 100 // This is the current schema's own value 103 101 values.push(value); 104 - continue; 105 - } 106 - 107 - // Check if the mapped schema extends the current schema 108 - const mappedSchema = context.resolveRef<SchemaObject>(mappedSchemaRef); 109 - if (mappedSchema.allOf) { 110 - for (const item of mappedSchema.allOf) { 111 - if ('$ref' in item && item.$ref === schemaRef) { 112 - // This schema extends the current schema, add its value 113 - values.push(value); 114 - break; 115 - } 116 - } 117 102 } 118 103 } 119 104 ··· 521 506 for (const { discriminator, isRequired, values } of discriminatorsToAdd) { 522 507 // Get all discriminator values including children for union types 523 508 const allValues = getAllDiscriminatorValues({ 524 - context, 525 509 discriminator, 526 510 schemaRef: state.$ref!, 527 511 }); ··· 543 527 logicalOperator: 'or', 544 528 } 545 529 : valueSchemas[0]!; 530 + 531 + // Check if any $ref schemas in schemaItems have this discriminator property 532 + // If yes, mark them to omit it to avoid conflicts 533 + for (const item of schemaItems) { 534 + if (item.$ref || item.symbolRef) { 535 + // Check if the referenced schema has this property 536 + const hasProperty = (() => { 537 + if (!item.$ref) return false; 538 + try { 539 + const refSchema = context.resolveRef<SchemaObject>(item.$ref); 540 + // Check if the discriminator property exists in the ref schema 541 + return ( 542 + refSchema.properties?.[discriminator.propertyName] !== 543 + undefined || 544 + (refSchema.allOf && 545 + refSchema.allOf.some((allOfItem) => { 546 + const resolved = 547 + '$ref' in allOfItem 548 + ? context.resolveRef<SchemaObject>(allOfItem.$ref) 549 + : allOfItem; 550 + return ( 551 + resolved.properties?.[discriminator.propertyName] !== 552 + undefined 553 + ); 554 + })) 555 + ); 556 + } catch { 557 + return false; 558 + } 559 + })(); 560 + 561 + if (hasProperty) { 562 + // Mark this ref to omit the discriminator property 563 + if (!item.omit) { 564 + item.omit = [discriminator.propertyName]; 565 + } else if (!item.omit.includes(discriminator.propertyName)) { 566 + item.omit = [...item.omit, discriminator.propertyName]; 567 + } 568 + } 569 + } 570 + } 546 571 547 572 // Find the inline schema (non-$ref) to merge the discriminator property into 548 573 // The inline schema should be the last non-$ref item in schemaItems
+42 -18
packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts
··· 84 84 }; 85 85 86 86 /** 87 - * Gets all discriminator values for a schema and its children in the inheritance hierarchy. 88 - * For intermediate schemas (those that are extended by others), returns a union of all values. 87 + * Gets the discriminator value for a schema. 88 + * Returns only the schema's own discriminator value, not child values. 89 89 */ 90 90 const getAllDiscriminatorValues = ({ 91 - context, 92 91 discriminator, 93 92 schemaRef, 94 93 }: { 95 - context: Context; 96 94 discriminator: NonNullable<SchemaObject['discriminator']>; 97 95 schemaRef: string; 98 96 }): Array<string> => { ··· 105 103 if (mappedSchemaRef === schemaRef) { 106 104 // This is the current schema's own value 107 105 values.push(value); 108 - continue; 109 - } 110 - 111 - // Check if the mapped schema extends the current schema 112 - const mappedSchema = context.resolveRef<SchemaObject>(mappedSchemaRef); 113 - if (mappedSchema.allOf) { 114 - for (const item of mappedSchema.allOf) { 115 - if (item.$ref && item.$ref === schemaRef) { 116 - // This schema extends the current schema, add its value 117 - values.push(value); 118 - break; 119 - } 120 - } 121 106 } 122 107 } 123 108 ··· 602 587 for (const { discriminator, isRequired, values } of discriminatorsToAdd) { 603 588 // Get all discriminator values including children for union types 604 589 const allValues = getAllDiscriminatorValues({ 605 - context, 606 590 discriminator, 607 591 schemaRef: state.$ref!, 608 592 }); ··· 624 608 logicalOperator: 'or', 625 609 } 626 610 : valueSchemas[0]!; 611 + 612 + // Check if any $ref schemas in schemaItems have this discriminator property 613 + // If yes, mark them to omit it to avoid conflicts 614 + for (const item of schemaItems) { 615 + if (item.$ref || item.symbolRef) { 616 + // Check if the referenced schema has this property 617 + const hasProperty = (() => { 618 + if (!item.$ref) return false; 619 + try { 620 + const refSchema = context.resolveRef<SchemaObject>(item.$ref); 621 + // Check if the discriminator property exists in the ref schema 622 + return ( 623 + refSchema.properties?.[discriminator.propertyName] !== 624 + undefined || 625 + (refSchema.allOf && 626 + refSchema.allOf.some((allOfItem) => { 627 + const resolved = allOfItem.$ref 628 + ? context.resolveRef<SchemaObject>(allOfItem.$ref) 629 + : allOfItem; 630 + return ( 631 + resolved.properties?.[discriminator.propertyName] !== 632 + undefined 633 + ); 634 + })) 635 + ); 636 + } catch { 637 + return false; 638 + } 639 + })(); 640 + 641 + if (hasProperty) { 642 + // Mark this ref to omit the discriminator property 643 + if (!item.omit) { 644 + item.omit = [discriminator.propertyName]; 645 + } else if (!item.omit.includes(discriminator.propertyName)) { 646 + item.omit = [...item.omit, discriminator.propertyName]; 647 + } 648 + } 649 + } 650 + } 627 651 628 652 // Find the inline schema (non-$ref) to merge the discriminator property into 629 653 // The inline schema should be the last non-$ref item in schemaItems
+20 -2
packages/openapi-ts/src/plugins/@hey-api/typescript/v1/plugin.ts
··· 24 24 schema: IR.SchemaObject; 25 25 }): MaybeTsDsl<TypeTsDsl> => { 26 26 if (schema.symbolRef) { 27 - return $.type(schema.symbolRef); 27 + const baseType = $.type(schema.symbolRef); 28 + if (schema.omit && schema.omit.length > 0) { 29 + // Render as Omit<Type, 'prop1' | 'prop2'> 30 + const omittedKeys = 31 + schema.omit.length === 1 32 + ? $.type.literal(schema.omit[0]!) 33 + : $.type.or(...schema.omit.map((key) => $.type.literal(key))); 34 + return $.type('Omit').generics(baseType, omittedKeys); 35 + } 36 + return baseType; 28 37 } 29 38 30 39 if (schema.$ref) { ··· 33 42 resource: 'definition', 34 43 resourceId: schema.$ref, 35 44 }); 36 - return $.type(symbol); 45 + const baseType = $.type(symbol); 46 + if (schema.omit && schema.omit.length > 0) { 47 + // Render as Omit<Type, 'prop1' | 'prop2'> 48 + const omittedKeys = 49 + schema.omit.length === 1 50 + ? $.type.literal(schema.omit[0]!) 51 + : $.type.or(...schema.omit.map((key) => $.type.literal(key))); 52 + return $.type('Omit').generics(baseType, omittedKeys); 53 + } 54 + return baseType; 37 55 } 38 56 39 57 if (schema.type) {