From cd105ef37ba068d75f8a38826785dfd60fefb1c7 Mon Sep 17 00:00:00 2001 From: Auke Schrijnen Date: Fri, 16 Apr 2021 15:26:30 +0200 Subject: [PATCH 1/2] [typescript-angular] Allow generating discriminator based type visitor When types which extend a common type and are distinguished based on a discriminator are consumed they are often cast to their specific type which results in error prone boilerplate code. By generating a visitor for those kind of types the various subtypes can be consumed in a type safe manner. --- docs/generators/typescript-angular.md | 1 + .../TypeScriptAngularClientCodegen.java | 15 +++++++++++++ .../typescript-angular/modelGeneric.mustache | 2 +- .../modelGenericVisitor.mustache | 22 +++++++++++++++++++ ...ypeScriptAngularClientOptionsProvider.java | 1 + 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache diff --git a/docs/generators/typescript-angular.md b/docs/generators/typescript-angular.md index 6916e3f662cc..cf236f6c3c4b 100644 --- a/docs/generators/typescript-angular.md +++ b/docs/generators/typescript-angular.md @@ -34,6 +34,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |stringEnums|Generate string enums instead of objects for enum values.| |false| +|subtypeVisitor|Use discriminators to create a subtype visitor (doesn't work with tagged unions).| |false| |supportsES6|Generate code that conforms to ES6.| |false| |taggedUnions|Use discriminators to create tagged unions instead of extending interfaces.| |false| |useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |false| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java index e91ddecda385..fb848b5af45a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java @@ -49,6 +49,7 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform} public static final String WITH_INTERFACES = "withInterfaces"; public static final String USE_SINGLE_REQUEST_PARAMETER = "useSingleRequestParameter"; public static final String TAGGED_UNIONS = "taggedUnions"; + public static final String SUBTYPE_VISITOR = "subtypeVisitor"; public static final String NG_VERSION = "ngVersion"; public static final String PROVIDED_IN_ROOT = "providedInRoot"; public static final String PROVIDED_IN = "providedIn"; @@ -77,6 +78,7 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform} protected PROVIDED_IN_LEVEL providedIn = PROVIDED_IN_LEVEL.root; private boolean taggedUnions = false; + private boolean subtypeVisitor = false; public TypeScriptAngularClientCodegen() { super(); @@ -106,6 +108,9 @@ public TypeScriptAngularClientCodegen() { this.cliOptions.add(CliOption.newBoolean(TAGGED_UNIONS, "Use discriminators to create tagged unions instead of extending interfaces.", this.taggedUnions)); + this.cliOptions.add(CliOption.newBoolean(SUBTYPE_VISITOR, + "Use discriminators to create a subtype visitor (doesn't work with tagged unions).", + this.subtypeVisitor)); this.cliOptions.add(CliOption.newBoolean(PROVIDED_IN_ROOT, "Use this property to provide Injectables in root (it is only valid in angular version greater or equal to 6.0.0). IMPORTANT: Deprecated for angular version greater or equal to 9.0.0, use **providedIn** instead.", false)); @@ -200,6 +205,10 @@ public void processOpts() { taggedUnions = Boolean.parseBoolean(additionalProperties.get(TAGGED_UNIONS).toString()); } + if (additionalProperties.containsKey(SUBTYPE_VISITOR)) { + subtypeVisitor = Boolean.parseBoolean(additionalProperties.get(SUBTYPE_VISITOR).toString()); + } + if (ngVersion.atLeast("9.0.0") && additionalProperties.containsKey(PROVIDED_IN)) { setProvidedIn(additionalProperties.get(PROVIDED_IN).toString()); } else { @@ -514,11 +523,17 @@ public Map postProcessAllModels(Map objs) { CodegenModel cm = (CodegenModel) mo.get("model"); if (taggedUnions) { mo.put(TAGGED_UNIONS, true); + } else if (subtypeVisitor) { + mo.put(SUBTYPE_VISITOR, true); + } + if (taggedUnions || subtypeVisitor) { if (cm.discriminator != null && cm.children != null) { for (CodegenModel child : cm.children) { cm.imports.add(child.classname); } } + } + if (taggedUnions) { if (cm.parent != null) { cm.imports.remove(cm.parent); } diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/modelGeneric.mustache index 93d0fb629db3..5e9a5b23bd21 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/modelGeneric.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/modelGeneric.mustache @@ -7,4 +7,4 @@ export interface {{classname}}{{#allParents}}{{#-first}} extends {{/-first}}{{{. {{/description}} {{#isReadOnly}}readonly {{/isReadOnly}}{{{name}}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; {{/vars}} -}{{>modelGenericEnums}} +}{{>modelGenericEnums}}{{>modelGenericVisitor}} diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache new file mode 100644 index 000000000000..f049c361406e --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache @@ -0,0 +1,22 @@ +{{#subtypeVisitor}} + {{#discriminator}} + + +export interface {{classname}}Visitor { + {{#discriminator.mappedModels}} + visit{{modelName}}(value: {{modelName}}): R; + {{/discriminator.mappedModels}} +} + +export function visit{{classname}}(value: {{classname}}, visitor: {{classname}}Visitor): R { + switch (value.{{discriminator.propertyName}}) { + {{#discriminator.mappedModels}} + case '{{mappingName}}': + return visitor.visit{{modelName}}(<{{modelName}}>value); + {{/discriminator.mappedModels}} + default: + throw new Error("Unknown model discriminator '" + value.{{discriminator.propertyName}} + "'"); + } +} + {{/discriminator}} +{{/subtypeVisitor}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java index eb8d90a468b8..8245e1ca2dd0 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java @@ -75,6 +75,7 @@ public Map createOptions() { .put(TypeScriptAngularClientCodegen.PROVIDED_IN_ROOT, Boolean.FALSE.toString()) .put(TypeScriptAngularClientCodegen.PROVIDED_IN, PROVIDED_IN_LEVEL) .put(TypeScriptAngularClientCodegen.TAGGED_UNIONS, Boolean.FALSE.toString()) + .put(TypeScriptAngularClientCodegen.SUBTYPE_VISITOR, Boolean.FALSE.toString()) .put(TypeScriptAngularClientCodegen.NPM_REPOSITORY, NPM_REPOSITORY) .put(TypeScriptAngularClientCodegen.NG_VERSION, NG_VERSION) .put(TypeScriptAngularClientCodegen.API_MODULE_PREFIX, API_MODULE_PREFIX) From 1f9402336d9ee743d0f59173807913aebaab6b1a Mon Sep 17 00:00:00 2001 From: Auke Schrijnen Date: Tue, 4 May 2021 08:50:10 +0200 Subject: [PATCH 2/2] [typescript-angular] Allow generating type visitor for tagged unions --- docs/generators/typescript-angular.md | 2 +- .../codegen/languages/TypeScriptAngularClientCodegen.java | 5 +++-- .../typescript-angular/modelGenericVisitor.mustache | 7 +++++++ .../resources/typescript-angular/modelTaggedUnion.mustache | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/generators/typescript-angular.md b/docs/generators/typescript-angular.md index cf236f6c3c4b..946700ad2ce1 100644 --- a/docs/generators/typescript-angular.md +++ b/docs/generators/typescript-angular.md @@ -34,7 +34,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| |stringEnums|Generate string enums instead of objects for enum values.| |false| -|subtypeVisitor|Use discriminators to create a subtype visitor (doesn't work with tagged unions).| |false| +|subtypeVisitor|Use discriminators to create a subtype visitor.| |false| |supportsES6|Generate code that conforms to ES6.| |false| |taggedUnions|Use discriminators to create tagged unions instead of extending interfaces.| |false| |useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |false| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java index fb848b5af45a..e912250ebbb9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java @@ -109,7 +109,7 @@ public TypeScriptAngularClientCodegen() { "Use discriminators to create tagged unions instead of extending interfaces.", this.taggedUnions)); this.cliOptions.add(CliOption.newBoolean(SUBTYPE_VISITOR, - "Use discriminators to create a subtype visitor (doesn't work with tagged unions).", + "Use discriminators to create a subtype visitor.", this.subtypeVisitor)); this.cliOptions.add(CliOption.newBoolean(PROVIDED_IN_ROOT, "Use this property to provide Injectables in root (it is only valid in angular version greater or equal to 6.0.0). IMPORTANT: Deprecated for angular version greater or equal to 9.0.0, use **providedIn** instead.", @@ -523,7 +523,8 @@ public Map postProcessAllModels(Map objs) { CodegenModel cm = (CodegenModel) mo.get("model"); if (taggedUnions) { mo.put(TAGGED_UNIONS, true); - } else if (subtypeVisitor) { + } + if (subtypeVisitor) { mo.put(SUBTYPE_VISITOR, true); } if (taggedUnions || subtypeVisitor) { diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache index f049c361406e..1695f2b2f3c2 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericVisitor.mustache @@ -15,7 +15,14 @@ export function visit{{classname}}(value: {{classname}}, visitor: {{classname return visitor.visit{{modelName}}(<{{modelName}}>value); {{/discriminator.mappedModels}} default: + {{#taggedUnions}} + // Make the compiler fail if there is an unmapped member + const _exhaustiveCheck: never = value; + throw new Error("Unknown model discriminator for '" + value + "'" + _exhaustiveCheck); + {{/taggedUnions}} + {{^taggedUnions}} throw new Error("Unknown model discriminator '" + value.{{discriminator.propertyName}} + "'"); + {{/taggedUnions}} } } {{/discriminator}} diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/modelTaggedUnion.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/modelTaggedUnion.mustache index 0189ea8b0ecd..0f0304ce43f1 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/modelTaggedUnion.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/modelTaggedUnion.mustache @@ -1,5 +1,6 @@ {{#discriminator}} export type {{classname}} = {{#children}}{{^-first}} | {{/-first}}{{classname}}{{/children}}; +{{>modelGenericVisitor}} {{/discriminator}} {{^discriminator}} {{#parent}}