Skip to content

[C#] Fixes array TypeDeclaration parsing order to fix deep aliases #21600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import java.util.stream.Collectors;

import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.ModelUtils.getSchemaItems;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;

Expand Down Expand Up @@ -1602,33 +1603,28 @@ public String getSchemaType(Schema p) {
/**
* Provides C# strongly typed declaration for simple arrays of some type and arrays of arrays of some type.
*
* @param arr The input array property
* @param items The input array property
* @return The type declaration when the type is an array of arrays.
*/
private String getArrayTypeDeclaration(Schema arr) {
// TODO: collection type here should be fully qualified namespace to avoid model conflicts
// This supports arrays of arrays.
String arrayType = typeMapping.get("array");
StringBuilder instantiationType = new StringBuilder(arrayType);
Schema<?> items = ModelUtils.getSchemaItems(arr);
String nestedType = getTypeDeclaration(items);
// TODO: We may want to differentiate here between generics and primitive arrays.
instantiationType.append("<").append(nestedType).append(">");
return instantiationType.toString();
private String getTypeDeclarationForArray(Schema<?> items) {
return getTypeDeclaration(items);
}

@Override
public String toInstantiationType(Schema p) {
if (ModelUtils.isArraySchema(p)) {
return getArrayTypeDeclaration(p);
return getTypeDeclarationForArray(p);
}
return super.toInstantiationType(p);
}

@Override
public String getTypeDeclaration(Schema p) {
if (ModelUtils.isArraySchema(p)) {
return getArrayTypeDeclaration(p);
Schema<?> schema = unaliasSchema(p);
Schema<?> target = ModelUtils.isGenerateAliasAsModel() ? p : schema;
if (ModelUtils.isArraySchema(target)) {
Schema<?> items = getSchemaItems(schema);
return getSchemaType(target) + "<" + getTypeDeclarationForArray(items) + ">";
} else if (ModelUtils.isMapSchema(p)) {
// Should we also support maps of maps?
Schema<?> inner = ModelUtils.getAdditionalProperties(p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1748,7 +1748,7 @@ public void postProcess() {

@Override
protected void updateModelForObject(CodegenModel m, Schema schema) {
/**
/*
* we have a custom version of this function so we only set isMap to true if
* ModelUtils.isMapSchema
* In other generators, isMap is true for all type object schemas
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,7 @@ protected void configureAdditionalPropertiesForFrameworks(final Map<String, Obje
@Override
public String toInstantiationType(Schema schema) {
if (ModelUtils.isMapSchema(schema)) {
Schema additionalProperties = ModelUtils.getAdditionalProperties(schema);
Schema<?> additionalProperties = ModelUtils.getAdditionalProperties(schema);
String inner = getSchemaType(additionalProperties);
if (ModelUtils.isMapSchema(additionalProperties)) {
inner = toInstantiationType(additionalProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1799,15 +1799,6 @@ public void testAllOfParent() {
assertEquals(getRequiredVars(personForUpdateModel), Collections.emptyList());
}

private List<String> getRequiredVars(CodegenModel model) {
return getNames(model.getRequiredVars());
}

private List<String> getNames(List<CodegenProperty> props) {
if (props == null) return null;
return props.stream().map(v -> v.name).collect(Collectors.toList());
}

@Test
public void testCallbacks() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/callbacks.yaml");
Expand Down Expand Up @@ -5017,4 +5008,13 @@ public void testQueryIsJsonMimeType() {

assertTrue(codegenOperation.queryParams.stream().allMatch(p -> p.queryIsJsonMimeType));
}

private List<String> getRequiredVars(CodegenModel model) {
return getNames(model.getRequiredVars());
}

private List<String> getNames(List<CodegenProperty> props) {
if (props == null) return null;
return props.stream().map(v -> v.name).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -775,36 +775,6 @@ public void testRecursionBug4650() {
// all fine, we have passed
}


private DefaultGenerator generatorGenerateRecursiveDependentModelsBackwardCompatibility(String recursively) throws IOException {
DefaultGenerator generator = new DefaultGenerator(false);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.API_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.API_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS, recursively);
return generator;
}

private ClientOptInput createOptInputIssue19220(Path target) {
return createOptInputIssue("19220", target);
}

private ClientOptInput createOptInputIssue18444(Path target) {
return createOptInputIssue("18444", target);
}

private ClientOptInput createOptInputIssue(String issueNumber, Path target) {
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("spring")
.setInputSpec("src/test/resources/bugs/issue_" + issueNumber + ".json")
.setOutputDir(target.toAbsolutePath().toString());
return configurator.toClientOptInput();
}

@Test
public void testGenerateRecursiveDependentModelsBackwardCompatibilityIssue18444() throws IOException {
Path target = Files.createTempDirectory("test");
Expand Down Expand Up @@ -1041,6 +1011,34 @@ public void testGenerateMultiLinePropertiesIssue19628() throws IOException {
GlobalSettings.reset();
output.deleteOnExit();
}
}

private DefaultGenerator generatorGenerateRecursiveDependentModelsBackwardCompatibility(String recursively) throws IOException {
DefaultGenerator generator = new DefaultGenerator(false);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.API_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.API_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS, recursively);
return generator;
}

private ClientOptInput createOptInputIssue19220(Path target) {
return createOptInputIssue("19220", target);
}

private ClientOptInput createOptInputIssue18444(Path target) {
return createOptInputIssue("18444", target);
}

private ClientOptInput createOptInputIssue(String issueNumber, Path target) {
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("spring")
.setInputSpec("src/test/resources/bugs/issue_" + issueNumber + ".json")
.setOutputDir(target.toAbsolutePath().toString());
return configurator.toClientOptInput();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,28 @@
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.CSharpClientCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.openapitools.codegen.TestUtils.assertFileContains;
import static org.openapitools.codegen.TestUtils.assertFileNotContains;

public class CSharpClientCodegenTest {

@Test
public void testToEnumVarName() throws Exception {
public void testToEnumVarName() {
final CSharpClientCodegen codegen = new CSharpClientCodegen();
codegen.setLibrary("restsharp");
codegen.processOpts();
Expand Down Expand Up @@ -211,4 +214,50 @@ public void testAnyOfDiscriminatorCreatesCompilableCode() throws IOException {
// Should not contain this as the constructor will have two parameters instead of one
assertFileNotContains(file.toPath(), "return new FruitAnyOfDisc(appleAnyOfDisc);");
}

@Test
public void testDoubleDepthArrayAliasCSharp() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/bugs/issue_21585.yaml");
String schemaName = "GeoJSON_MultiLineString";
String modelName = "GeoJSONMultiLineString";
Schema schema = ModelUtils.getSchema(openAPI, schemaName);

CSharpClientCodegen codegen = new CSharpClientCodegen();
codegen.setOpenAPI(openAPI);
CodegenModel concreteModel = codegen.fromModel(modelName, schema);
assertThat(getNames(concreteModel.vars)).isEqualTo(List.of("Type", "Coordinates", "Bbox"));
assertThat(concreteModel.vars.get(1).getDataType()).isEqualTo("List<List<List<BigDecimal>>>");
}

@Test
public void testDeepArrayAlias() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/bugs/issue_21585.yaml");
final DefaultGenerator defaultGenerator = new DefaultGenerator();
final ClientOptInput clientOptInput = new ClientOptInput();
clientOptInput.openAPI(openAPI);
CSharpClientCodegen cSharpClientCodegen = new CSharpClientCodegen();
cSharpClientCodegen.setLibrary("httpclient");
cSharpClientCodegen.setOutputDir(output.getAbsolutePath());
cSharpClientCodegen.setAutosetConstants(true);
clientOptInput.config(cSharpClientCodegen);
defaultGenerator.opts(clientOptInput);

Map<String, File> files = defaultGenerator.generate().stream()
.collect(Collectors.toMap(File::getPath, Function.identity()));

String modelName = "GeoJSONMultiLineString";
File file = files.get(Paths
.get(output.getAbsolutePath(), "src", "Org.OpenAPITools", "Model", modelName + ".cs")
.toString()
);
assertNotNull(file, "Could not find file for model: " + modelName);
assertFileContains(file.toPath(), "public List<List<List<decimal>>> Coordinates { get; set; }");
}

private List<String> getNames(List<CodegenProperty> props) {
if (props == null) return null;
return props.stream().map(v -> v.name).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ public void nullablePropertyWithNullableReferenceTypesTest() {

final CodegenProperty property2 = cm.vars.get(1);
Assert.assertEquals(property2.baseName, "urls");
Assert.assertEquals(property2.dataType, "List<string>");
Assert.assertEquals(property2.dataType, "List?<string>");
Assert.assertEquals(property2.name, "Urls");
Assert.assertNull(property2.defaultValue);
Assert.assertEquals(property2.baseType, "List?");
Expand Down
Loading
Loading