[typescript-angular] taggedUnions=true: Support recursive schemas (#11016)

* typescript-angular: taggedUnions=true: Preserve import to the parent model from the child
if the child property has a direct reference to it

* Fix javadoc
This commit is contained in:
Alexey Makhrov
2021-12-04 08:43:52 -08:00
committed by GitHub
parent 96ee13bbb2
commit 03b4ac736f
4 changed files with 151 additions and 23 deletions

View File

@@ -570,14 +570,7 @@ public class DefaultCodegen implements CodegenConfig {
List<Map<String, Object>> models = (List<Map<String, Object>>) inner.get("models");
for (Map<String, Object> mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");
for (CodegenProperty cp : cm.allVars) {
// detect self import
if (cp.dataType.equalsIgnoreCase(cm.classname) ||
(cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(cm.classname))) {
cm.imports.remove(cm.classname); // remove self import
cp.isSelfReference = true;
}
}
removeSelfReferenceImports(cm);
}
}
setCircularReferences(allModels);
@@ -585,6 +578,23 @@ public class DefaultCodegen implements CodegenConfig {
return objs;
}
/**
* Removes imports from the model that points to itself
* Marks a self referencing property, if detected
*
* @param model Self imports will be removed from this model.imports collection
*/
protected void removeSelfReferenceImports(CodegenModel model) {
for (CodegenProperty cp : model.allVars) {
// detect self import
if (cp.dataType.equalsIgnoreCase(model.classname) ||
(cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(model.classname))) {
model.imports.remove(model.classname); // remove self import
cp.isSelfReference = true;
}
}
}
public void setCircularReferences(Map<String, CodegenModel> models) {
final Map<String, List<CodegenProperty>> dependencyMap = models.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> getModelDependencies(entry.getValue())));
@@ -5168,17 +5178,7 @@ public class DefaultCodegen implements CodegenConfig {
cm.hasOnlyReadOnly = false;
}
// TODO revise the logic to include map
if (cp.isContainer) {
addImport(cm, typeMapping.get("array"));
}
addImport(cm, cp.baseType);
CodegenProperty innerCp = cp;
while (innerCp != null) {
addImport(cm, innerCp.complexType);
innerCp = innerCp.items;
}
addImportsForPropertyType(cm, cp);
// if required, add to the list "requiredVars"
if (Boolean.TRUE.equals(cp.required)) {
@@ -5199,6 +5199,28 @@ public class DefaultCodegen implements CodegenConfig {
return;
}
/**
* For a given property, adds all needed imports to the model
* This includes a flat property type (e.g. property type: ReferencedModel)
* as well as container type (property type: array of ReferencedModel's)
*
* @param model The codegen representation of the OAS schema.
* @param property The codegen representation of the OAS schema's property.
*/
protected void addImportsForPropertyType(CodegenModel model, CodegenProperty property) {
// TODO revise the logic to include map
if (property.isContainer) {
addImport(model, typeMapping.get("array"));
}
addImport(model, property.baseType);
CodegenProperty innerCp = property;
while (innerCp != null) {
addImport(model, innerCp.complexType);
innerCp = innerCp.items;
}
}
/**
* Determine all of the types in the model definitions (schemas) that are aliases of
* simple types.

View File

@@ -549,8 +549,19 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
setChildDiscriminatorValue(cm, child);
}
}
// with tagged union, a child model doesn't extend the parent (all properties are just copied over)
// it means we don't need to import that parent any more
if (cm.parent != null) {
cm.imports.remove(cm.parent);
// however, it's possible that the child model contains a recursive reference to the parent
// in order to support this case, we update the list of imports from properties once again
for (CodegenProperty cp: cm.allVars) {
addImportsForPropertyType(cm, cp);
}
removeSelfReferenceImports(cm);
}
}
// Add additional filename information for imports

View File

@@ -8,14 +8,18 @@ import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.languages.TypeScriptAngularClientCodegen;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class TypeScriptAngularClientCodegenTest {
@Test
@@ -267,4 +271,44 @@ public class TypeScriptAngularClientCodegenTest {
codegen.importMapping().put(importedModel, importName);
Assert.assertEquals(codegen.toModelImport(importedModel), importName);
}
@Test
public void testTaggedUnionImports() throws Exception {
final String specPath = "src/test/resources/3_0/allOf_composition_discriminator_recursive.yaml";
Map<String, Object> properties = new HashMap<>();
properties.put(TypeScriptAngularClientCodegen.TAGGED_UNIONS, "true");
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("typescript-angular")
.setInputSpec(specPath)
.setAdditionalProperties(properties)
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
final ClientOptInput clientOptInput = configurator.toClientOptInput();
Generator generator = new DefaultGenerator();
generator.opts(clientOptInput).generate();
TestUtils.assertFileContains(
Paths.get(output + "/model/expressionToken.ts"),
"import { Token } from './token'", // imports the parent schema
"import { TokenMetadata } from './tokenMetadata'", // imports a schema referenced in an inherited property
"export interface ExpressionToken {" // no inheritance
);
TestUtils.assertFileNotContains(
Paths.get(output + "/model/stringToken.ts"),
"import { Token } from './token'"
);
TestUtils.assertFileContains(
Paths.get(output + "/model/token.ts"),
"import { ExpressionToken } from './expressionToken'",
"export type Token = ExpressionToken | StringToken"
);
}
}

View File

@@ -0,0 +1,51 @@
openapi: 3.0.2
info:
title: OAI Specification example for Polymorphism
version: 1.0.0
paths:
/status:
get:
responses:
'201':
description: desc
components:
schemas:
TokenMetadata:
type: object
properties:
tag1:
type: string
Token:
type: object
required:
- type
properties:
type:
type: string
metadata:
$ref: '#/components/schemas/TokenMetadata'
discriminator:
propertyName: type
mapping:
string: '#/components/schemas/StringToken'
expression: '#/components/schemas/ExpressionToken'
StringToken:
allOf:
- $ref: '#/components/schemas/Token'
- type: object
properties:
value:
type: string
ExpressionToken:
allOf:
- $ref: '#/components/schemas/Token'
- type: object
properties:
tokens:
type: array
items:
$ref: '#/components/schemas/Token'