Skip to content

Commit e694589

Browse files
authored
Merge pull request ferdikoomen#741 from sjoerdmulder/feature/discriminator
Adding oneOf with discriminator property support
2 parents d11d7c1 + d82f3eb commit e694589

File tree

8 files changed

+240
-46
lines changed

8 files changed

+240
-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: 46 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,82 @@ 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),
8085
imports: model.imports,
8186
enum: model.enum,
8287
enums: model.enums,
8388
properties: model.properties,
89+
...propertyValues,
8490
});
8591
}
8692
}

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 description}}
66
description: '{{{escapeSinglequotes description}}}',

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
@@ -3214,6 +3214,7 @@ export type { CompositionWithAnyOfAnonymous } from './models/CompositionWithAnyO
32143214
export type { CompositionWithOneOf } from './models/CompositionWithOneOf';
32153215
export type { CompositionWithOneOfAndNullable } from './models/CompositionWithOneOfAndNullable';
32163216
export type { CompositionWithOneOfAnonymous } from './models/CompositionWithOneOfAnonymous';
3217+
export type { CompositionWithOneOfDiscriminator } from './models/CompositionWithOneOfDiscriminator';
32173218
export type { DictionaryWithArray } from './models/DictionaryWithArray';
32183219
export type { DictionaryWithDictionary } from './models/DictionaryWithDictionary';
32193220
export type { DictionaryWithProperties } from './models/DictionaryWithProperties';
@@ -3223,6 +3224,8 @@ export type { EnumFromDescription } from './models/EnumFromDescription';
32233224
export { EnumWithExtensions } from './models/EnumWithExtensions';
32243225
export { EnumWithNumbers } from './models/EnumWithNumbers';
32253226
export { EnumWithStrings } from './models/EnumWithStrings';
3227+
export type { ModelCircle } from './models/ModelCircle';
3228+
export type { ModelSquare } from './models/ModelSquare';
32263229
export type { ModelThatExtends } from './models/ModelThatExtends';
32273230
export type { ModelThatExtendsExtends } from './models/ModelThatExtendsExtends';
32283231
export type { ModelWithArray } from './models/ModelWithArray';
@@ -3265,6 +3268,7 @@ export { $CompositionWithAnyOfAnonymous } from './schemas/$CompositionWithAnyOfA
32653268
export { $CompositionWithOneOf } from './schemas/$CompositionWithOneOf';
32663269
export { $CompositionWithOneOfAndNullable } from './schemas/$CompositionWithOneOfAndNullable';
32673270
export { $CompositionWithOneOfAnonymous } from './schemas/$CompositionWithOneOfAnonymous';
3271+
export { $CompositionWithOneOfDiscriminator } from './schemas/$CompositionWithOneOfDiscriminator';
32683272
export { $DictionaryWithArray } from './schemas/$DictionaryWithArray';
32693273
export { $DictionaryWithDictionary } from './schemas/$DictionaryWithDictionary';
32703274
export { $DictionaryWithProperties } from './schemas/$DictionaryWithProperties';
@@ -3274,6 +3278,8 @@ export { $EnumFromDescription } from './schemas/$EnumFromDescription';
32743278
export { $EnumWithExtensions } from './schemas/$EnumWithExtensions';
32753279
export { $EnumWithNumbers } from './schemas/$EnumWithNumbers';
32763280
export { $EnumWithStrings } from './schemas/$EnumWithStrings';
3281+
export { $ModelCircle } from './schemas/$ModelCircle';
3282+
export { $ModelSquare } from './schemas/$ModelSquare';
32773283
export { $ModelThatExtends } from './schemas/$ModelThatExtends';
32783284
export { $ModelThatExtendsExtends } from './schemas/$ModelThatExtendsExtends';
32793285
export { $ModelWithArray } from './schemas/$ModelWithArray';
@@ -3558,6 +3564,21 @@ export type CompositionWithOneOfAnonymous = {
35583564
"
35593565
`;
35603566

3567+
exports[`v3 should generate: ./test/generated/v3/models/CompositionWithOneOfDiscriminator.ts 1`] = `
3568+
"/* istanbul ignore file */
3569+
/* tslint:disable */
3570+
/* eslint-disable */
3571+
3572+
import type { ModelCircle } from './ModelCircle';
3573+
import type { ModelSquare } from './ModelSquare';
3574+
3575+
/**
3576+
* This is a model with one property with a 'one of' relationship where the options are not $ref
3577+
*/
3578+
export type CompositionWithOneOfDiscriminator = (ModelCircle | ModelSquare);
3579+
"
3580+
`;
3581+
35613582
exports[`v3 should generate: ./test/generated/v3/models/DictionaryWithArray.ts 1`] = `
35623583
"/* istanbul ignore file */
35633584
/* tslint:disable */
@@ -3697,6 +3718,36 @@ export enum EnumWithStrings {
36973718
}"
36983719
`;
36993720

3721+
exports[`v3 should generate: ./test/generated/v3/models/ModelCircle.ts 1`] = `
3722+
"/* istanbul ignore file */
3723+
/* tslint:disable */
3724+
/* eslint-disable */
3725+
3726+
/**
3727+
* Circle
3728+
*/
3729+
export type ModelCircle = {
3730+
kind: 'circle';
3731+
radius?: number;
3732+
}
3733+
"
3734+
`;
3735+
3736+
exports[`v3 should generate: ./test/generated/v3/models/ModelSquare.ts 1`] = `
3737+
"/* istanbul ignore file */
3738+
/* tslint:disable */
3739+
/* eslint-disable */
3740+
3741+
/**
3742+
* Square
3743+
*/
3744+
export type ModelSquare = {
3745+
kind: 'square';
3746+
sideLength?: number;
3747+
}
3748+
"
3749+
`;
3750+
37003751
exports[`v3 should generate: ./test/generated/v3/models/ModelThatExtends.ts 1`] = `
37013752
"/* istanbul ignore file */
37023753
/* tslint:disable */
@@ -4482,6 +4533,20 @@ export const $CompositionWithOneOfAnonymous = {
44824533
} as const;"
44834534
`;
44844535

4536+
exports[`v3 should generate: ./test/generated/v3/schemas/$CompositionWithOneOfDiscriminator.ts 1`] = `
4537+
"/* istanbul ignore file */
4538+
/* tslint:disable */
4539+
/* eslint-disable */
4540+
export const $CompositionWithOneOfDiscriminator = {
4541+
type: 'one-of',
4542+
contains: [{
4543+
type: 'ModelCircle',
4544+
}, {
4545+
type: 'ModelSquare',
4546+
}],
4547+
} as const;"
4548+
`;
4549+
44854550
exports[`v3 should generate: ./test/generated/v3/schemas/$DictionaryWithArray.ts 1`] = `
44864551
"/* istanbul ignore file */
44874552
/* tslint:disable */
@@ -4592,6 +4657,40 @@ export const $EnumWithStrings = {
45924657
} as const;"
45934658
`;
45944659

4660+
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelCircle.ts 1`] = `
4661+
"/* istanbul ignore file */
4662+
/* tslint:disable */
4663+
/* eslint-disable */
4664+
export const $ModelCircle = {
4665+
properties: {
4666+
kind: {
4667+
type: 'string',
4668+
isRequired: true,
4669+
},
4670+
radius: {
4671+
type: 'number',
4672+
},
4673+
},
4674+
} as const;"
4675+
`;
4676+
4677+
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelSquare.ts 1`] = `
4678+
"/* istanbul ignore file */
4679+
/* tslint:disable */
4680+
/* eslint-disable */
4681+
export const $ModelSquare = {
4682+
properties: {
4683+
kind: {
4684+
type: 'string',
4685+
isRequired: true,
4686+
},
4687+
sideLength: {
4688+
type: 'number',
4689+
},
4690+
},
4691+
} as const;"
4692+
`;
4693+
45954694
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelThatExtends.ts 1`] = `
45964695
"/* istanbul ignore file */
45974696
/* tslint:disable */

0 commit comments

Comments
 (0)