Skip to content

Commit a543b9d

Browse files
committed
Adding discriminator support for oneOf
1 parent 1508588 commit a543b9d

File tree

8 files changed

+241
-46
lines changed

8 files changed

+241
-46
lines changed

src/openApi/v2/parser/getOperationParameterName.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import camelCase from 'camelcase';
22

3-
const reservedWords =
4-
/^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g;
3+
const reservedWords = /^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g;
54

65
/**
76
* Replaces any invalid characters from a parameter name.

src/openApi/v3/parser/getModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export function getModel(
165165
model.default = getModelDefault(definition, model);
166166

167167
if (definition.properties) {
168-
const modelProperties = getModelProperties(openApi, definition, getModel);
168+
const modelProperties = getModelProperties(openApi, definition, getModel, model);
169169
modelProperties.forEach(modelProperty => {
170170
model.imports.push(...modelProperty.imports);
171171
model.enums.push(...modelProperty.enums);

src/openApi/v3/parser/getModelProperties.ts

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Model } from '../../../client/interfaces/Model';
2+
import { findOneOfParentDiscriminator, mapPropertyValue } from '../../../utils/discriminator';
23
import { getPattern } from '../../../utils/getPattern';
34
import type { OpenApi } from '../interfaces/OpenApi';
45
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
@@ -10,77 +11,83 @@ import { getType } from './getType';
1011
// Fix for circular dependency
1112
export type GetModelFn = typeof getModel;
1213

13-
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema, getModel: GetModelFn): Model[] {
14+
export function getModelProperties(
15+
openApi: OpenApi,
16+
definition: OpenApiSchema,
17+
getModel: GetModelFn,
18+
parent?: Model
19+
): Model[] {
1420
const models: Model[] = [];
21+
const discriminator = findOneOfParentDiscriminator(openApi, parent);
1522
for (const propertyName in definition.properties) {
1623
if (definition.properties.hasOwnProperty(propertyName)) {
1724
const property = definition.properties[propertyName];
1825
const propertyRequired = !!definition.required?.includes(propertyName);
19-
if (property.$ref) {
26+
const propertyValues = {
27+
name: escapeName(propertyName),
28+
description: getComment(property.description),
29+
isDefinition: false,
30+
isReadOnly: property.readOnly === true,
31+
isRequired: propertyRequired,
32+
format: property.format,
33+
maximum: property.maximum,
34+
exclusiveMaximum: property.exclusiveMaximum,
35+
minimum: property.minimum,
36+
exclusiveMinimum: property.exclusiveMinimum,
37+
multipleOf: property.multipleOf,
38+
maxLength: property.maxLength,
39+
minLength: property.minLength,
40+
maxItems: property.maxItems,
41+
minItems: property.minItems,
42+
uniqueItems: property.uniqueItems,
43+
maxProperties: property.maxProperties,
44+
minProperties: property.minProperties,
45+
pattern: getPattern(property.pattern),
46+
};
47+
if (parent && discriminator?.propertyName == propertyName) {
48+
models.push({
49+
export: 'reference',
50+
type: 'string',
51+
base: `'${mapPropertyValue(discriminator, parent)}'`,
52+
template: null,
53+
isNullable: property.nullable === true,
54+
link: null,
55+
imports: [],
56+
enum: [],
57+
enums: [],
58+
properties: [],
59+
...propertyValues,
60+
});
61+
} else if (property.$ref) {
2062
const model = getType(property.$ref);
2163
models.push({
22-
name: escapeName(propertyName),
2364
export: 'reference',
2465
type: model.type,
2566
base: model.base,
2667
template: model.template,
2768
link: null,
28-
description: getComment(property.description),
29-
isDefinition: false,
30-
isReadOnly: property.readOnly === true,
31-
isRequired: propertyRequired,
3269
isNullable: model.isNullable || property.nullable === true,
33-
format: property.format,
34-
maximum: property.maximum,
35-
exclusiveMaximum: property.exclusiveMaximum,
36-
minimum: property.minimum,
37-
exclusiveMinimum: property.exclusiveMinimum,
38-
multipleOf: property.multipleOf,
39-
maxLength: property.maxLength,
40-
minLength: property.minLength,
41-
maxItems: property.maxItems,
42-
minItems: property.minItems,
43-
uniqueItems: property.uniqueItems,
44-
maxProperties: property.maxProperties,
45-
minProperties: property.minProperties,
46-
pattern: getPattern(property.pattern),
4770
imports: model.imports,
4871
enum: [],
4972
enums: [],
5073
properties: [],
74+
...propertyValues,
5175
});
5276
} else {
5377
const model = getModel(openApi, property);
5478
models.push({
55-
name: escapeName(propertyName),
5679
export: model.export,
5780
type: model.type,
5881
base: model.base,
5982
template: model.template,
6083
link: model.link,
61-
description: getComment(property.description),
62-
isDefinition: false,
63-
isReadOnly: property.readOnly === true,
64-
isRequired: propertyRequired,
6584
isNullable: model.isNullable || property.nullable === true,
66-
format: property.format,
67-
maximum: property.maximum,
68-
exclusiveMaximum: property.exclusiveMaximum,
69-
minimum: property.minimum,
70-
exclusiveMinimum: property.exclusiveMinimum,
71-
multipleOf: property.multipleOf,
72-
maxLength: property.maxLength,
73-
minLength: property.minLength,
74-
maxItems: property.maxItems,
75-
minItems: property.minItems,
76-
uniqueItems: property.uniqueItems,
77-
maxProperties: property.maxProperties,
78-
minProperties: property.minProperties,
79-
pattern: getPattern(property.pattern),
85+
8086
imports: model.imports,
8187
enum: model.enum,
8288
enums: model.enums,
8389
properties: model.properties,
90+
...propertyValues,
8491
});
8592
}
8693
}

src/openApi/v3/parser/getOperationParameterName.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import camelCase from 'camelcase';
22

3-
const reservedWords =
4-
/^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g;
3+
const reservedWords = /^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g;
54

65
/**
76
* Replaces any invalid characters from a parameter name.

src/templates/partials/schemaGeneric.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
{{#if type}}
3-
type: '{{{base}}}',
3+
type: '{{{type}}}',
44
{{/if}}
55
{{#if isReadOnly}}
66
isReadOnly: {{{isReadOnly}}},

src/utils/discriminator.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Model } from '../client/interfaces/Model';
2+
import { OpenApi } from '../openApi/v3/interfaces/OpenApi';
3+
import { OpenApiDiscriminator } from '../openApi/v3/interfaces/OpenApiDiscriminator';
4+
import { stripNamespace } from '../openApi/v3/parser/stripNamespace';
5+
import { Dictionary } from './types';
6+
7+
const inverseDictionary = (mapObj: Dictionary<string>) => {
8+
const m2: Dictionary<string> = {};
9+
for (const key in mapObj) {
10+
m2[mapObj[key]] = key;
11+
}
12+
return m2;
13+
};
14+
15+
export function findOneOfParentDiscriminator(openApi: OpenApi, parent?: Model): OpenApiDiscriminator | undefined {
16+
if (openApi.components) {
17+
for (const definitionName in openApi.components.schemas) {
18+
if (openApi.components.schemas.hasOwnProperty(definitionName)) {
19+
const schema = openApi.components.schemas[definitionName];
20+
if (parent && schema.oneOf?.length && schema.discriminator) {
21+
const isPartOf =
22+
schema.oneOf
23+
.map(definition => {
24+
return definition.$ref && stripNamespace(definition.$ref) == parent.name;
25+
})
26+
.filter(Boolean).length > 0;
27+
if (isPartOf) {
28+
return schema.discriminator;
29+
}
30+
}
31+
}
32+
}
33+
}
34+
return undefined;
35+
}
36+
37+
export function mapPropertyValue(discriminator: OpenApiDiscriminator, parent: Model): string {
38+
if (discriminator.mapping) {
39+
const mapping = inverseDictionary(discriminator.mapping);
40+
const key = Object.keys(mapping).find(item => stripNamespace(item) == parent.name);
41+
if (key && mapping[key]) {
42+
return mapping[key];
43+
}
44+
}
45+
return parent.name;
46+
}

test/__snapshots__/index.spec.js.snap

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3169,6 +3169,7 @@ export type { CompositionWithAnyOfAnonymous } from './models/CompositionWithAnyO
31693169
export type { CompositionWithOneOf } from './models/CompositionWithOneOf';
31703170
export type { CompositionWithOneOfAndNullable } from './models/CompositionWithOneOfAndNullable';
31713171
export type { CompositionWithOneOfAnonymous } from './models/CompositionWithOneOfAnonymous';
3172+
export type { CompositionWithOneOfDiscriminator } from './models/CompositionWithOneOfDiscriminator';
31723173
export type { DictionaryWithArray } from './models/DictionaryWithArray';
31733174
export type { DictionaryWithDictionary } from './models/DictionaryWithDictionary';
31743175
export type { DictionaryWithProperties } from './models/DictionaryWithProperties';
@@ -3178,6 +3179,8 @@ export type { EnumFromDescription } from './models/EnumFromDescription';
31783179
export { EnumWithExtensions } from './models/EnumWithExtensions';
31793180
export { EnumWithNumbers } from './models/EnumWithNumbers';
31803181
export { EnumWithStrings } from './models/EnumWithStrings';
3182+
export type { ModelCircle } from './models/ModelCircle';
3183+
export type { ModelSquare } from './models/ModelSquare';
31813184
export type { ModelThatExtends } from './models/ModelThatExtends';
31823185
export type { ModelThatExtendsExtends } from './models/ModelThatExtendsExtends';
31833186
export type { ModelWithArray } from './models/ModelWithArray';
@@ -3220,6 +3223,7 @@ export { $CompositionWithAnyOfAnonymous } from './schemas/$CompositionWithAnyOfA
32203223
export { $CompositionWithOneOf } from './schemas/$CompositionWithOneOf';
32213224
export { $CompositionWithOneOfAndNullable } from './schemas/$CompositionWithOneOfAndNullable';
32223225
export { $CompositionWithOneOfAnonymous } from './schemas/$CompositionWithOneOfAnonymous';
3226+
export { $CompositionWithOneOfDiscriminator } from './schemas/$CompositionWithOneOfDiscriminator';
32233227
export { $DictionaryWithArray } from './schemas/$DictionaryWithArray';
32243228
export { $DictionaryWithDictionary } from './schemas/$DictionaryWithDictionary';
32253229
export { $DictionaryWithProperties } from './schemas/$DictionaryWithProperties';
@@ -3229,6 +3233,8 @@ export { $EnumFromDescription } from './schemas/$EnumFromDescription';
32293233
export { $EnumWithExtensions } from './schemas/$EnumWithExtensions';
32303234
export { $EnumWithNumbers } from './schemas/$EnumWithNumbers';
32313235
export { $EnumWithStrings } from './schemas/$EnumWithStrings';
3236+
export { $ModelCircle } from './schemas/$ModelCircle';
3237+
export { $ModelSquare } from './schemas/$ModelSquare';
32323238
export { $ModelThatExtends } from './schemas/$ModelThatExtends';
32333239
export { $ModelThatExtendsExtends } from './schemas/$ModelThatExtendsExtends';
32343240
export { $ModelWithArray } from './schemas/$ModelWithArray';
@@ -3513,6 +3519,21 @@ export type CompositionWithOneOfAnonymous = {
35133519
"
35143520
`;
35153521

3522+
exports[`v3 should generate: ./test/generated/v3/models/CompositionWithOneOfDiscriminator.ts 1`] = `
3523+
"/* istanbul ignore file */
3524+
/* tslint:disable */
3525+
/* eslint-disable */
3526+
3527+
import type { ModelCircle } from './ModelCircle';
3528+
import type { ModelSquare } from './ModelSquare';
3529+
3530+
/**
3531+
* This is a model with one property with a 'one of' relationship where the options are not $ref
3532+
*/
3533+
export type CompositionWithOneOfDiscriminator = (ModelCircle | ModelSquare);
3534+
"
3535+
`;
3536+
35163537
exports[`v3 should generate: ./test/generated/v3/models/DictionaryWithArray.ts 1`] = `
35173538
"/* istanbul ignore file */
35183539
/* tslint:disable */
@@ -3652,6 +3673,36 @@ export enum EnumWithStrings {
36523673
}"
36533674
`;
36543675

3676+
exports[`v3 should generate: ./test/generated/v3/models/ModelCircle.ts 1`] = `
3677+
"/* istanbul ignore file */
3678+
/* tslint:disable */
3679+
/* eslint-disable */
3680+
3681+
/**
3682+
* Circle
3683+
*/
3684+
export type ModelCircle = {
3685+
kind: 'circle';
3686+
radius?: number;
3687+
}
3688+
"
3689+
`;
3690+
3691+
exports[`v3 should generate: ./test/generated/v3/models/ModelSquare.ts 1`] = `
3692+
"/* istanbul ignore file */
3693+
/* tslint:disable */
3694+
/* eslint-disable */
3695+
3696+
/**
3697+
* Square
3698+
*/
3699+
export type ModelSquare = {
3700+
kind: 'square';
3701+
sideLength?: number;
3702+
}
3703+
"
3704+
`;
3705+
36553706
exports[`v3 should generate: ./test/generated/v3/models/ModelThatExtends.ts 1`] = `
36563707
"/* istanbul ignore file */
36573708
/* tslint:disable */
@@ -4422,6 +4473,20 @@ export const $CompositionWithOneOfAnonymous = {
44224473
} as const;"
44234474
`;
44244475

4476+
exports[`v3 should generate: ./test/generated/v3/schemas/$CompositionWithOneOfDiscriminator.ts 1`] = `
4477+
"/* istanbul ignore file */
4478+
/* tslint:disable */
4479+
/* eslint-disable */
4480+
export const $CompositionWithOneOfDiscriminator = {
4481+
type: 'one-of',
4482+
contains: [{
4483+
type: 'ModelCircle',
4484+
}, {
4485+
type: 'ModelSquare',
4486+
}],
4487+
} as const;"
4488+
`;
4489+
44254490
exports[`v3 should generate: ./test/generated/v3/schemas/$DictionaryWithArray.ts 1`] = `
44264491
"/* istanbul ignore file */
44274492
/* tslint:disable */
@@ -4531,6 +4596,40 @@ export const $EnumWithStrings = {
45314596
} as const;"
45324597
`;
45334598

4599+
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelCircle.ts 1`] = `
4600+
"/* istanbul ignore file */
4601+
/* tslint:disable */
4602+
/* eslint-disable */
4603+
export const $ModelCircle = {
4604+
properties: {
4605+
kind: {
4606+
type: 'string',
4607+
isRequired: true,
4608+
},
4609+
radius: {
4610+
type: 'number',
4611+
},
4612+
},
4613+
} as const;"
4614+
`;
4615+
4616+
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelSquare.ts 1`] = `
4617+
"/* istanbul ignore file */
4618+
/* tslint:disable */
4619+
/* eslint-disable */
4620+
export const $ModelSquare = {
4621+
properties: {
4622+
kind: {
4623+
type: 'string',
4624+
isRequired: true,
4625+
},
4626+
sideLength: {
4627+
type: 'number',
4628+
},
4629+
},
4630+
} as const;"
4631+
`;
4632+
45344633
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelThatExtends.ts 1`] = `
45354634
"/* istanbul ignore file */
45364635
/* tslint:disable */

0 commit comments

Comments
 (0)