Merge pull request #741 from sjoerdmulder/feature/discriminator

Adding oneOf with discriminator property support
This commit is contained in:
Ferdi Koomen 2021-12-22 11:29:57 -05:00 committed by GitHub
commit e6945897d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 240 additions and 46 deletions

View File

@ -1,7 +1,6 @@
import camelCase from 'camelcase';
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;
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;
/**
* Replaces any invalid characters from a parameter name.

View File

@ -165,7 +165,7 @@ export function getModel(
model.default = getModelDefault(definition, model);
if (definition.properties) {
const modelProperties = getModelProperties(openApi, definition, getModel);
const modelProperties = getModelProperties(openApi, definition, getModel, model);
modelProperties.forEach(modelProperty => {
model.imports.push(...modelProperty.imports);
model.enums.push(...modelProperty.enums);

View File

@ -1,4 +1,5 @@
import type { Model } from '../../../client/interfaces/Model';
import { findOneOfParentDiscriminator, mapPropertyValue } from '../../../utils/discriminator';
import { getPattern } from '../../../utils/getPattern';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
@ -10,77 +11,82 @@ import { getType } from './getType';
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema, getModel: GetModelFn): Model[] {
export function getModelProperties(
openApi: OpenApi,
definition: OpenApiSchema,
getModel: GetModelFn,
parent?: Model
): Model[] {
const models: Model[] = [];
const discriminator = findOneOfParentDiscriminator(openApi, parent);
for (const propertyName in definition.properties) {
if (definition.properties.hasOwnProperty(propertyName)) {
const property = definition.properties[propertyName];
const propertyRequired = !!definition.required?.includes(propertyName);
if (property.$ref) {
const propertyValues = {
name: escapeName(propertyName),
description: getComment(property.description),
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired,
format: property.format,
maximum: property.maximum,
exclusiveMaximum: property.exclusiveMaximum,
minimum: property.minimum,
exclusiveMinimum: property.exclusiveMinimum,
multipleOf: property.multipleOf,
maxLength: property.maxLength,
minLength: property.minLength,
maxItems: property.maxItems,
minItems: property.minItems,
uniqueItems: property.uniqueItems,
maxProperties: property.maxProperties,
minProperties: property.minProperties,
pattern: getPattern(property.pattern),
};
if (parent && discriminator?.propertyName == propertyName) {
models.push({
export: 'reference',
type: 'string',
base: `'${mapPropertyValue(discriminator, parent)}'`,
template: null,
isNullable: property.nullable === true,
link: null,
imports: [],
enum: [],
enums: [],
properties: [],
...propertyValues,
});
} else if (property.$ref) {
const model = getType(property.$ref);
models.push({
name: escapeName(propertyName),
export: 'reference',
type: model.type,
base: model.base,
template: model.template,
link: null,
description: getComment(property.description),
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired,
isNullable: model.isNullable || property.nullable === true,
format: property.format,
maximum: property.maximum,
exclusiveMaximum: property.exclusiveMaximum,
minimum: property.minimum,
exclusiveMinimum: property.exclusiveMinimum,
multipleOf: property.multipleOf,
maxLength: property.maxLength,
minLength: property.minLength,
maxItems: property.maxItems,
minItems: property.minItems,
uniqueItems: property.uniqueItems,
maxProperties: property.maxProperties,
minProperties: property.minProperties,
pattern: getPattern(property.pattern),
imports: model.imports,
enum: [],
enums: [],
properties: [],
...propertyValues,
});
} else {
const model = getModel(openApi, property);
models.push({
name: escapeName(propertyName),
export: model.export,
type: model.type,
base: model.base,
template: model.template,
link: model.link,
description: getComment(property.description),
isDefinition: false,
isReadOnly: property.readOnly === true,
isRequired: propertyRequired,
isNullable: model.isNullable || property.nullable === true,
format: property.format,
maximum: property.maximum,
exclusiveMaximum: property.exclusiveMaximum,
minimum: property.minimum,
exclusiveMinimum: property.exclusiveMinimum,
multipleOf: property.multipleOf,
maxLength: property.maxLength,
minLength: property.minLength,
maxItems: property.maxItems,
minItems: property.minItems,
uniqueItems: property.uniqueItems,
maxProperties: property.maxProperties,
minProperties: property.minProperties,
pattern: getPattern(property.pattern),
imports: model.imports,
enum: model.enum,
enums: model.enums,
properties: model.properties,
...propertyValues,
});
}
}

View File

@ -1,7 +1,6 @@
import camelCase from 'camelcase';
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;
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;
/**
* Replaces any invalid characters from a parameter name.

View File

@ -1,6 +1,6 @@
{
{{#if type}}
type: '{{{base}}}',
type: '{{{type}}}',
{{/if}}
{{#if description}}
description: '{{{escapeSinglequotes description}}}',

View File

@ -0,0 +1,46 @@
import { Model } from '../client/interfaces/Model';
import { OpenApi } from '../openApi/v3/interfaces/OpenApi';
import { OpenApiDiscriminator } from '../openApi/v3/interfaces/OpenApiDiscriminator';
import { stripNamespace } from '../openApi/v3/parser/stripNamespace';
import { Dictionary } from './types';
const inverseDictionary = (mapObj: Dictionary<string>) => {
const m2: Dictionary<string> = {};
for (const key in mapObj) {
m2[mapObj[key]] = key;
}
return m2;
};
export function findOneOfParentDiscriminator(openApi: OpenApi, parent?: Model): OpenApiDiscriminator | undefined {
if (openApi.components) {
for (const definitionName in openApi.components.schemas) {
if (openApi.components.schemas.hasOwnProperty(definitionName)) {
const schema = openApi.components.schemas[definitionName];
if (parent && schema.oneOf?.length && schema.discriminator) {
const isPartOf =
schema.oneOf
.map(definition => {
return definition.$ref && stripNamespace(definition.$ref) == parent.name;
})
.filter(Boolean).length > 0;
if (isPartOf) {
return schema.discriminator;
}
}
}
}
}
return undefined;
}
export function mapPropertyValue(discriminator: OpenApiDiscriminator, parent: Model): string {
if (discriminator.mapping) {
const mapping = inverseDictionary(discriminator.mapping);
const key = Object.keys(mapping).find(item => stripNamespace(item) == parent.name);
if (key && mapping[key]) {
return mapping[key];
}
}
return parent.name;
}

View File

@ -3214,6 +3214,7 @@ export type { CompositionWithAnyOfAnonymous } from './models/CompositionWithAnyO
export type { CompositionWithOneOf } from './models/CompositionWithOneOf';
export type { CompositionWithOneOfAndNullable } from './models/CompositionWithOneOfAndNullable';
export type { CompositionWithOneOfAnonymous } from './models/CompositionWithOneOfAnonymous';
export type { CompositionWithOneOfDiscriminator } from './models/CompositionWithOneOfDiscriminator';
export type { DictionaryWithArray } from './models/DictionaryWithArray';
export type { DictionaryWithDictionary } from './models/DictionaryWithDictionary';
export type { DictionaryWithProperties } from './models/DictionaryWithProperties';
@ -3223,6 +3224,8 @@ export type { EnumFromDescription } from './models/EnumFromDescription';
export { EnumWithExtensions } from './models/EnumWithExtensions';
export { EnumWithNumbers } from './models/EnumWithNumbers';
export { EnumWithStrings } from './models/EnumWithStrings';
export type { ModelCircle } from './models/ModelCircle';
export type { ModelSquare } from './models/ModelSquare';
export type { ModelThatExtends } from './models/ModelThatExtends';
export type { ModelThatExtendsExtends } from './models/ModelThatExtendsExtends';
export type { ModelWithArray } from './models/ModelWithArray';
@ -3265,6 +3268,7 @@ export { $CompositionWithAnyOfAnonymous } from './schemas/$CompositionWithAnyOfA
export { $CompositionWithOneOf } from './schemas/$CompositionWithOneOf';
export { $CompositionWithOneOfAndNullable } from './schemas/$CompositionWithOneOfAndNullable';
export { $CompositionWithOneOfAnonymous } from './schemas/$CompositionWithOneOfAnonymous';
export { $CompositionWithOneOfDiscriminator } from './schemas/$CompositionWithOneOfDiscriminator';
export { $DictionaryWithArray } from './schemas/$DictionaryWithArray';
export { $DictionaryWithDictionary } from './schemas/$DictionaryWithDictionary';
export { $DictionaryWithProperties } from './schemas/$DictionaryWithProperties';
@ -3274,6 +3278,8 @@ export { $EnumFromDescription } from './schemas/$EnumFromDescription';
export { $EnumWithExtensions } from './schemas/$EnumWithExtensions';
export { $EnumWithNumbers } from './schemas/$EnumWithNumbers';
export { $EnumWithStrings } from './schemas/$EnumWithStrings';
export { $ModelCircle } from './schemas/$ModelCircle';
export { $ModelSquare } from './schemas/$ModelSquare';
export { $ModelThatExtends } from './schemas/$ModelThatExtends';
export { $ModelThatExtendsExtends } from './schemas/$ModelThatExtendsExtends';
export { $ModelWithArray } from './schemas/$ModelWithArray';
@ -3558,6 +3564,21 @@ export type CompositionWithOneOfAnonymous = {
"
`;
exports[`v3 should generate: ./test/generated/v3/models/CompositionWithOneOfDiscriminator.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { ModelCircle } from './ModelCircle';
import type { ModelSquare } from './ModelSquare';
/**
* This is a model with one property with a 'one of' relationship where the options are not $ref
*/
export type CompositionWithOneOfDiscriminator = (ModelCircle | ModelSquare);
"
`;
exports[`v3 should generate: ./test/generated/v3/models/DictionaryWithArray.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
@ -3697,6 +3718,36 @@ export enum EnumWithStrings {
}"
`;
exports[`v3 should generate: ./test/generated/v3/models/ModelCircle.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Circle
*/
export type ModelCircle = {
kind: 'circle';
radius?: number;
}
"
`;
exports[`v3 should generate: ./test/generated/v3/models/ModelSquare.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Square
*/
export type ModelSquare = {
kind: 'square';
sideLength?: number;
}
"
`;
exports[`v3 should generate: ./test/generated/v3/models/ModelThatExtends.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
@ -4482,6 +4533,20 @@ export const $CompositionWithOneOfAnonymous = {
} as const;"
`;
exports[`v3 should generate: ./test/generated/v3/schemas/$CompositionWithOneOfDiscriminator.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $CompositionWithOneOfDiscriminator = {
type: 'one-of',
contains: [{
type: 'ModelCircle',
}, {
type: 'ModelSquare',
}],
} as const;"
`;
exports[`v3 should generate: ./test/generated/v3/schemas/$DictionaryWithArray.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
@ -4592,6 +4657,40 @@ export const $EnumWithStrings = {
} as const;"
`;
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelCircle.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ModelCircle = {
properties: {
kind: {
type: 'string',
isRequired: true,
},
radius: {
type: 'number',
},
},
} as const;"
`;
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelSquare.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $ModelSquare = {
properties: {
kind: {
type: 'string',
isRequired: true,
},
sideLength: {
type: 'number',
},
},
} as const;"
`;
exports[`v3 should generate: ./test/generated/v3/schemas/$ModelThatExtends.ts 1`] = `
"/* istanbul ignore file */
/* tslint:disable */

View File

@ -1863,6 +1863,51 @@
}
}
},
"ModelCircle": {
"description": "Circle",
"type": "object",
"required": ["kind"],
"properties": {
"kind": {
"type": "string"
},
"radius": {
"type": "number"
}
}
},
"ModelSquare": {
"description": "Square",
"type": "object",
"required": ["kind"],
"properties": {
"kind": {
"type": "string"
},
"sideLength": {
"type": "number"
}
}
},
"CompositionWithOneOfDiscriminator": {
"description": "This is a model with one property with a 'one of' relationship where the options are not $ref",
"type": "object",
"oneOf": [
{
"$ref": "#/components/schemas/ModelCircle"
},
{
"$ref": "#/components/schemas/ModelSquare"
}
],
"discriminator": {
"propertyName": "kind",
"mapping": {
"circle": "#/components/schemas/ModelCircle",
"square": "#/components/schemas/ModelSquare"
}
}
},
"CompositionWithAnyOf": {
"description": "This is a model with one property with a 'any of' relationship",
"type": "object",