Add nullable annotation support to AspNetCoreServer (#9620)

* Add nullable annotation support to AspNetCoreServer

* Adjust naming for compatability with PR #9235 by @dehl-labs
This commit is contained in:
Michael Janssen
2021-06-02 05:54:16 +02:00
committed by GitHub
parent 0f51662650
commit b9e75951e7
4 changed files with 147 additions and 1 deletions

View File

@@ -19,6 +19,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|licenseUrl|The URL of the license| |http://localhost|
|modelClassModifier|Model Class Modifier can be nothing or partial| |partial|
|newtonsoftVersion|Version for Microsoft.AspNetCore.Mvc.NewtonsoftJson for ASP.NET Core 3.0+| |3.0.0|
|nullableReferenceTypes|Annotate Project with <Nullable>annotations</Nullable> and use ? annotation on all nullable attributes. Only supported on C# 8 / ASP.NET Core 3.0 or newer.| |false|
|operationIsAsync|Set methods to async or sync (default).| |false|
|operationModifier|Operation Modifier can be virtual or abstract|<dl><dt>**virtual**</dt><dd>Keep method virtual</dd><dt>**abstract**</dt><dd>Make method abstract</dd></dl>|virtual|
|operationResultTask|Set methods result to Task&lt;&gt;.| |false|

View File

@@ -60,6 +60,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
public static final String USE_NEWTONSOFT = "useNewtonsoft";
public static final String USE_DEFAULT_ROUTING = "useDefaultRouting";
public static final String NEWTONSOFT_VERSION = "newtonsoftVersion";
public static final String NULLABLE_REFERENCE_TYPES = "nullableReferenceTypes";
private String packageGuid = "{" + randomUUID().toString().toUpperCase(Locale.ROOT) + "}";
private String userSecretsGuid = randomUUID().toString();
@@ -84,6 +85,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
private boolean useFrameworkReference = false;
private boolean useNewtonsoft = true;
private boolean useDefaultRouting = true;
private boolean nullableReferenceTypes = false;
private String newtonsoftVersion = "3.0.0";
public AspNetCoreServerCodegen() {
@@ -249,6 +251,11 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
"Use default routing for the ASP.NET Core version.",
useDefaultRouting);
addSwitch(NULLABLE_REFERENCE_TYPES,
"Annotate Project with <Nullable>annotations</Nullable> and use ? annotation on all nullable attributes. " +
"Only supported on C# 8 / ASP.NET Core 3.0 or newer.",
nullableReferenceTypes);
addOption(CodegenConstants.ENUM_NAME_SUFFIX,
CodegenConstants.ENUM_NAME_SUFFIX_DESC,
enumNameSuffix);
@@ -366,6 +373,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
setIsFramework();
setUseNewtonsoft();
setUseEndpointRouting();
setNullableReferenceTypes();
supportingFiles.add(new SupportingFile("build.sh.mustache", "", "build.sh"));
supportingFiles.add(new SupportingFile("build.bat.mustache", "", "build.bat"));
@@ -520,7 +528,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
@Override
public String getNullableType(Schema p, String type) {
if (languageSpecificPrimitives.contains(type)) {
if (isSupportNullable() && ModelUtils.isNullable(p) && nullableType.contains(type)) {
if (isSupportNullable() && ModelUtils.isNullable(p) && (nullableType.contains(type) || nullableReferenceTypes)) {
return type + "?";
} else {
return type;
@@ -649,6 +657,19 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
}
}
private void setNullableReferenceTypes() {
if (additionalProperties.containsKey(NULLABLE_REFERENCE_TYPES)) {
if (aspnetCoreVersion.getOptValue().startsWith("2.")) {
LOGGER.warn("Nullable annotation are not supported in ASP.NET core version 2. Setting " + NULLABLE_REFERENCE_TYPES + " to false");
additionalProperties.put(NULLABLE_REFERENCE_TYPES, false);
} else {
nullableReferenceTypes = convertPropertyToBooleanAndWriteBack(NULLABLE_REFERENCE_TYPES);
}
} else {
additionalProperties.put(NULLABLE_REFERENCE_TYPES, nullableReferenceTypes);
}
}
private void setOperationIsAsync() {
if (isLibrary) {
operationIsAsync = false;

View File

@@ -7,6 +7,9 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PreserveCompilationContext>true</PreserveCompilationContext>
<Version>{{packageVersion}}</Version>
{{#nullableReferenceTypes}}
<Nullable>annotations</Nullable>
{{/nullableReferenceTypes}}
{{#isLibrary}}
<OutputType>Library</OutputType>
{{/isLibrary}}

View File

@@ -25,6 +25,7 @@ import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.DefaultCodegen;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.languages.AspNetCoreServerCodegen;
import org.openapitools.codegen.languages.CSharpClientCodegen;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -264,6 +265,126 @@ public class CSharpModelTest {
Assert.assertTrue(property3.isPrimitiveType);
}
@Test(description = "convert a model with a nullable property without nullable annotation")
public void nullablePropertyWithoutNullableReferenceTypesTest() {
final Schema model = new Schema()
.description("a sample model")
.addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT).nullable(true))
.addProperties("urls", new ArraySchema()
.items(new StringSchema()).nullable(true))
.addProperties("name", new StringSchema().nullable(true))
.addProperties("subObject", new Schema().addProperties("name", new StringSchema()).nullable(true))
.addRequiredItem("id");
final DefaultCodegen codegen = new AspNetCoreServerCodegen();
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
codegen.setOpenAPI(openAPI);
final CodegenModel cm = codegen.fromModel("sample", model);
Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertEquals(cm.description, "a sample model");
Assert.assertEquals(cm.vars.size(), 4);
final CodegenProperty property1 = cm.vars.get(0);
Assert.assertEquals(property1.baseName, "id");
Assert.assertEquals(property1.dataType, "long?");
Assert.assertEquals(property1.name, "Id");
Assert.assertNull(property1.defaultValue);
Assert.assertEquals(property1.baseType, "long?");
Assert.assertTrue(property1.required);
Assert.assertTrue(property1.isPrimitiveType);
final CodegenProperty property2 = cm.vars.get(1);
Assert.assertEquals(property2.baseName, "urls");
Assert.assertEquals(property2.dataType, "List<string>");
Assert.assertEquals(property2.name, "Urls");
Assert.assertNull(property2.defaultValue);
Assert.assertEquals(property2.baseType, "List");
Assert.assertEquals(property2.containerType, "array");
Assert.assertFalse(property2.required);
Assert.assertTrue(property2.isPrimitiveType);
Assert.assertTrue(property2.isContainer);
final CodegenProperty property3 = cm.vars.get(2);
Assert.assertEquals(property3.baseName, "name");
Assert.assertEquals(property3.dataType, "string");
Assert.assertEquals(property3.name, "Name");
Assert.assertNull(property3.defaultValue);
Assert.assertEquals(property3.baseType, "string");
Assert.assertFalse(property3.required);
Assert.assertTrue(property3.isPrimitiveType);
final CodegenProperty property4 = cm.vars.get(3);
Assert.assertEquals(property4.baseName, "subObject");
Assert.assertEquals(property4.dataType, "Object");
Assert.assertEquals(property4.name, "SubObject");
Assert.assertNull(property4.defaultValue);
Assert.assertEquals(property4.baseType, "Object");
Assert.assertFalse(property4.required);
Assert.assertTrue(property4.isPrimitiveType);
}
@Test(description = "convert a model with a nullable property using nullable annotation")
public void nullablePropertyWithNullableReferenceTypesTest() {
final Schema model = new Schema()
.description("a sample model")
.addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT).nullable(true))
.addProperties("urls", new ArraySchema()
.items(new StringSchema()).nullable(true))
.addProperties("name", new StringSchema().nullable(true))
.addProperties("subObject", new Schema().addProperties("name", new StringSchema()).nullable(true))
.addRequiredItem("id");
final DefaultCodegen codegen = new AspNetCoreServerCodegen();
codegen.additionalProperties().put(AspNetCoreServerCodegen.NULLABLE_REFERENCE_TYPES, true);
codegen.processOpts();
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
codegen.setOpenAPI(openAPI);
final CodegenModel cm = codegen.fromModel("sample", model);
Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertEquals(cm.description, "a sample model");
Assert.assertEquals(cm.vars.size(), 4);
final CodegenProperty property1 = cm.vars.get(0);
Assert.assertEquals(property1.baseName, "id");
Assert.assertEquals(property1.dataType, "long?");
Assert.assertEquals(property1.name, "Id");
Assert.assertNull(property1.defaultValue);
Assert.assertEquals(property1.baseType, "long?");
Assert.assertTrue(property1.required);
Assert.assertTrue(property1.isPrimitiveType);
final CodegenProperty property2 = cm.vars.get(1);
Assert.assertEquals(property2.baseName, "urls");
Assert.assertEquals(property2.dataType, "List<string>");
Assert.assertEquals(property2.name, "Urls");
Assert.assertNull(property2.defaultValue);
Assert.assertEquals(property2.baseType, "List?");
Assert.assertEquals(property2.containerType, "array");
Assert.assertFalse(property2.required);
Assert.assertTrue(property2.isPrimitiveType);
Assert.assertTrue(property2.isContainer);
final CodegenProperty property3 = cm.vars.get(2);
Assert.assertEquals(property3.baseName, "name");
Assert.assertEquals(property3.dataType, "string?");
Assert.assertEquals(property3.name, "Name");
Assert.assertNull(property3.defaultValue);
Assert.assertEquals(property3.baseType, "string?");
Assert.assertFalse(property3.required);
Assert.assertFalse(property3.isPrimitiveType);
final CodegenProperty property4 = cm.vars.get(3);
Assert.assertEquals(property4.baseName, "subObject");
Assert.assertEquals(property4.dataType, "Object?");
Assert.assertEquals(property4.name, "SubObject");
Assert.assertNull(property4.defaultValue);
Assert.assertEquals(property4.baseType, "Object?");
Assert.assertFalse(property4.required);
Assert.assertFalse(property4.isPrimitiveType);
}
@Test(description = "convert a model with list property")
public void listPropertyTest() {
final Schema model = new Schema()