+5
.changeset/happy-points-run.md
+5
.changeset/happy-points-run.md
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-all-of/types.gen.ts
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-all-of/types.gen.ts
···
8
8
id: string;
9
9
};
10
10
11
-
export type Bar = Foo & {
11
+
export type Bar = Omit<Foo, 'id'> & {
12
12
bar?: string;
13
13
id: 'Bar';
14
14
};
15
15
16
-
export type Baz = Foo & {
16
+
export type Baz = Omit<Foo, 'id'> & {
17
17
baz?: string;
18
18
id: 'Baz';
19
19
};
20
20
21
-
export type Qux = Foo & {
21
+
export type Qux = Omit<Foo, 'id'> & {
22
22
qux?: boolean;
23
23
id: 'Qux';
24
24
};
···
27
27
id: string;
28
28
};
29
29
30
-
export type BarMapped = FooMapped & {
30
+
export type BarMapped = Omit<FooMapped, 'id'> & {
31
31
bar?: string;
32
32
id: 'bar';
33
33
};
34
34
35
-
export type BazMapped = FooMapped & {
35
+
export type BazMapped = Omit<FooMapped, 'id'> & {
36
36
baz?: string;
37
37
id: 'baz';
38
38
};
39
39
40
-
export type QuxMapped = FooMapped & {
40
+
export type QuxMapped = Omit<FooMapped, 'id'> & {
41
41
qux?: boolean;
42
42
id: 'QuxMapped';
43
43
};
+3
-3
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-allof-nested/types.gen.ts
+3
-3
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/discriminator-allof-nested/types.gen.ts
···
9
9
id: number;
10
10
};
11
11
12
-
export type CarDto = VehicleDto & {
12
+
export type CarDto = Omit<VehicleDto, '$type'> & {
13
13
modelName: string;
14
-
$type: 'Car' | 'Volvo';
14
+
$type: 'Car';
15
15
};
16
16
17
-
export type VolvoDto = CarDto & {
17
+
export type VolvoDto = Omit<CarDto, '$type'> & {
18
18
seatbeltCount: number;
19
19
$type: 'Volvo';
20
20
};
+1
-1
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-all-of/types.gen.ts
+1
-1
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-all-of/types.gen.ts
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-all-of/types.gen.ts
+6
-6
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-all-of/types.gen.ts
···
8
8
id: string;
9
9
};
10
10
11
-
export type Bar = Foo & {
11
+
export type Bar = Omit<Foo, 'id'> & {
12
12
bar?: string;
13
13
id: 'Bar';
14
14
};
15
15
16
-
export type Baz = Foo & {
16
+
export type Baz = Omit<Foo, 'id'> & {
17
17
baz?: string;
18
18
id: 'Baz';
19
19
};
20
20
21
-
export type Qux = Foo & {
21
+
export type Qux = Omit<Foo, 'id'> & {
22
22
qux?: boolean;
23
23
id: 'Qux';
24
24
};
···
27
27
id: string;
28
28
};
29
29
30
-
export type BarMapped = FooMapped & {
30
+
export type BarMapped = Omit<FooMapped, 'id'> & {
31
31
bar?: string;
32
32
id: 'bar';
33
33
};
34
34
35
-
export type BazMapped = FooMapped & {
35
+
export type BazMapped = Omit<FooMapped, 'id'> & {
36
36
baz?: string;
37
37
id: 'baz';
38
38
};
39
39
40
-
export type QuxMapped = FooMapped & {
40
+
export type QuxMapped = Omit<FooMapped, 'id'> & {
41
41
qux?: boolean;
42
42
id: 'QuxMapped';
43
43
};
+3
-3
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-allof-nested/types.gen.ts
+3
-3
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/discriminator-allof-nested/types.gen.ts
···
9
9
id: number;
10
10
};
11
11
12
-
export type CarDto = VehicleDto & {
12
+
export type CarDto = Omit<VehicleDto, '$type'> & {
13
13
modelName: string;
14
-
$type: 'Car' | 'Volvo';
14
+
$type: 'Car';
15
15
};
16
16
17
-
export type VolvoDto = CarDto & {
17
+
export type VolvoDto = Omit<CarDto, '$type'> & {
18
18
seatbeltCount: number;
19
19
$type: 'Volvo';
20
20
};
+1
-1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-all-of/types.gen.ts
+1
-1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-all-of/types.gen.ts
+5
packages/openapi-ts/src/ir/types.d.ts
+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
+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
+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
+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) {