From fc58a17e18bea299f6aeb6f1090569443eccaafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augustin=20=C5=A0ulc?= Date: Tue, 2 Feb 2021 14:03:14 +0100 Subject: [PATCH] Updated AllOf support in OpenAPI generator --- packages/apiclient/src/restRequestBuilder.ts | 13 +++++--- .../generator/src/openapi/fileGenerator.ts | 13 ++------ .../src/openapi/models/inheritedEntity.ts | 9 +++--- .../src/openapi/parsers/openApi2Parser.ts | 32 +++++++++++++++---- .../src/openapi/parsers/openApi3Parser.ts | 32 +++++++++++++++---- .../src/automaticEntityValidator.ts | 1 + 6 files changed, 69 insertions(+), 31 deletions(-) diff --git a/packages/apiclient/src/restRequestBuilder.ts b/packages/apiclient/src/restRequestBuilder.ts index 0e4605e8..c399620d 100644 --- a/packages/apiclient/src/restRequestBuilder.ts +++ b/packages/apiclient/src/restRequestBuilder.ts @@ -123,11 +123,16 @@ export class RestRequestBuilder { return this; } - getQueryString(query: any) { - return stringify(query, this.queryStringOptions ?? RestRequestBuilder.DefaultQueryStringOptions); + getQueryString(query: any, queryStringOptions?: StringifyOptions) { + return stringify(query, queryStringOptions ?? this.queryStringOptions ?? RestRequestBuilder.DefaultQueryStringOptions); } - appendQuery(url: string, query?: any) { - return query ? `${url}?${this.getQueryString(query)}` : url; + appendQuery(url: string, query?: any, queryStringOptions?: StringifyOptions) { + if (!query) { + return url; + } + + const queryString = typeof query === "string" ? query : this.getQueryString(query, queryStringOptions); + return `${url}?${queryString}`; } } diff --git a/packages/generator/src/openapi/fileGenerator.ts b/packages/generator/src/openapi/fileGenerator.ts index 50055961..9e4c7648 100644 --- a/packages/generator/src/openapi/fileGenerator.ts +++ b/packages/generator/src/openapi/fileGenerator.ts @@ -29,10 +29,11 @@ export default class FileGenerator { for (const { type } of items) { if (type instanceof Enum) { enumWriter.write(type); + } else if (type instanceof InheritedEntity) { + const baseEntity = type.baseEntities.map(x => x.type).filter(x => x instanceof ObjectEntity)[0] as ObjectEntity; + objectWriter.write(type, baseEntity); } else if (type instanceof ObjectEntity) { objectWriter.write(type); - } else if (type instanceof InheritedEntity) { - this.handleInheritedEntity(objectWriter, type); } else if (type instanceof UnionEntity) { unionWriter.write(type); } @@ -46,12 +47,4 @@ export default class FileGenerator { progress.stop(); } - - private handleInheritedEntity(writer: ObjectEntityWriter, source: InheritedEntity) { - const objects = source.baseEntities.filter(x => x.type instanceof ObjectEntity).map(x => x.type as ObjectEntity); - - const properties = objects.slice(1).flatMap(x => x.properties); - const composedEntity = new ObjectEntity(source.name, properties); - writer.write(composedEntity, objects[0]); - } } diff --git a/packages/generator/src/openapi/models/inheritedEntity.ts b/packages/generator/src/openapi/models/inheritedEntity.ts index 8b456441..6f34ac60 100644 --- a/packages/generator/src/openapi/models/inheritedEntity.ts +++ b/packages/generator/src/openapi/models/inheritedEntity.ts @@ -1,8 +1,9 @@ -import NamedObject from "./namedObject"; +import EntityProperty from "./entityProperty"; +import ObjectEntity from "./objectEntity"; import TypeReference from "./typeReference"; -export default class InheritedEntity extends NamedObject { - constructor(name: string, public baseEntities: TypeReference[]) { - super(name); +export default class InheritedEntity extends ObjectEntity { + constructor(name: string, public baseEntities: TypeReference[], properties: EntityProperty[]) { + super(name, properties); } } diff --git a/packages/generator/src/openapi/parsers/openApi2Parser.ts b/packages/generator/src/openapi/parsers/openApi2Parser.ts index 9f75a2b1..2d84f354 100644 --- a/packages/generator/src/openapi/parsers/openApi2Parser.ts +++ b/packages/generator/src/openapi/parsers/openApi2Parser.ts @@ -95,17 +95,31 @@ export default class OpenApi2Parser { private parseAllOfObject(name: string, definition: OpenAPIV2.SchemaObject) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const innerTypes = definition.allOf!.map((x, i) => - this.parseSchemaObject(`${name}Parent${i + 1}`, x as OpenAPIV2.SchemaObject | OpenAPIV2.ItemsObject) + const subTypes = definition.allOf!; + + const plainObjects = subTypes.filter( + x => !isV2ReferenceObject(x) && x.type === "object" && !x.allOf && !x.oneOf + ) as OpenAPIV2.SchemaObject[]; + + const otherParents = subTypes + .filter(x => !plainObjects.includes(x as any)) + .map((x, i) => this.parseSchemaObject(`${name}Parent${i + 1}`, x as OpenAPIV2.SchemaObject)); + + const properties = plainObjects.flatMap(x => this.extractObjectProperties(name, x)); + + const entity = new InheritedEntity(name, otherParents, properties); + + plainObjects.forEach(object => + object.required?.forEach(property => entity.addPropertyRestriction(property, Restriction.required, true)) ); - const entity = new InheritedEntity(name, innerTypes); return this.setTypeReference(name, entity); } private parseOneOfObject(name: string, definition: OpenAPIV2.SchemaObject) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const innerTypes = definition.oneOf!.map((x, i) => + const subTypes = definition.oneOf!; + const innerTypes = subTypes.map((x, i) => this.parseSchemaObject(`${name}Option${i + 1}`, x as OpenAPIV2.SchemaObject | OpenAPIV2.ItemsObject) ); @@ -114,9 +128,7 @@ export default class OpenApi2Parser { } private parseObjectWithProperties(name: string, definition: OpenAPIV2.SchemaObject) { - const properties = definition.properties - ? Object.entries(definition.properties).map(x => this.parseEntityProperty(name, x[0], x[1])) - : []; + const properties = this.extractObjectProperties(name, definition); const entity = new ObjectEntity(name, properties); definition.required?.forEach(property => entity.addPropertyRestriction(property, Restriction.required, true)); @@ -124,6 +136,12 @@ export default class OpenApi2Parser { return this.setTypeReference(name, entity); } + private extractObjectProperties(entityName: string, definition: OpenAPIV2.SchemaObject) { + return definition.properties + ? Object.entries(definition.properties).map(x => this.parseEntityProperty(entityName, x[0], x[1])) + : []; + } + private parseEntityProperty( entityName: string, propertyName: string, diff --git a/packages/generator/src/openapi/parsers/openApi3Parser.ts b/packages/generator/src/openapi/parsers/openApi3Parser.ts index 26caf0d0..a611d570 100644 --- a/packages/generator/src/openapi/parsers/openApi3Parser.ts +++ b/packages/generator/src/openapi/parsers/openApi3Parser.ts @@ -91,24 +91,38 @@ export default class OpenApi3Parser { private parseAllOfObject(name: string, definition: OpenAPIV3.SchemaObject) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const innerTypes = definition.allOf!.map((x, i) => this.parseSchemaObject(`${name}Parent${i + 1}`, x)); + const subTypes = definition.allOf!; + + const plainObjects = subTypes.filter( + x => !isV3ReferenceObject(x) && x.type === "object" && !x.allOf && !x.oneOf + ) as OpenAPIV3.SchemaObject[]; + + const otherParents = subTypes + .filter(x => !plainObjects.includes(x as any)) + .map((x, i) => this.parseSchemaObject(`${name}Parent${i + 1}`, x)); + + const properties = plainObjects.flatMap(x => this.extractObjectProperties(name, x)); + + const entity = new InheritedEntity(name, otherParents, properties); + + plainObjects.forEach(object => + object.required?.forEach(property => entity.addPropertyRestriction(property, Restriction.required, true)) + ); - const entity = new InheritedEntity(name, innerTypes); return this.setTypeReference(name, entity); } private parseOneOfObject(name: string, definition: OpenAPIV3.SchemaObject) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const innerTypes = definition.oneOf!.map((x, i) => this.parseSchemaObject(`${name}Option${i + 1}`, x)); + const subTypes = definition.oneOf!; + const innerTypes = subTypes.map((x, i) => this.parseSchemaObject(`${name}Option${i + 1}`, x)); const entity = new UnionEntity(name, innerTypes); return this.setTypeReference(name, entity); } private parseObjectWithProperties(name: string, definition: OpenAPIV3.SchemaObject) { - const properties = definition.properties - ? Object.entries(definition.properties).map(x => this.parseEntityProperty(name, x[0], x[1])) - : []; + const properties = this.extractObjectProperties(name, definition); const entity = new ObjectEntity(name, properties); definition.required?.forEach(property => entity.addPropertyRestriction(property, Restriction.required, true)); @@ -116,6 +130,12 @@ export default class OpenApi3Parser { return this.setTypeReference(name, entity); } + private extractObjectProperties(entityName: string, definition: OpenAPIV3.SchemaObject) { + return definition.properties + ? Object.entries(definition.properties).map(x => this.parseEntityProperty(entityName, x[0], x[1])) + : []; + } + private parseEntityProperty( entityName: string, propertyName: string, diff --git a/packages/validation/src/automaticEntityValidator.ts b/packages/validation/src/automaticEntityValidator.ts index 2d4165db..19a48989 100644 --- a/packages/validation/src/automaticEntityValidator.ts +++ b/packages/validation/src/automaticEntityValidator.ts @@ -50,6 +50,7 @@ export default class AutomaticEntityValidator