Add nullable support to C# client (refactor) (#1775)

* add nullable support to c# client (refactor)

* clean up methods

* move typemapping to constructor
This commit is contained in:
William Cheng
2018-12-31 10:44:02 +08:00
committed by GitHub
parent 5730f6224a
commit 2f6381cb19
54 changed files with 472 additions and 349 deletions

View File

@@ -75,6 +75,12 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
protected Set<String> collectionTypes;
protected Set<String> mapTypes;
// true if support nullable type
protected boolean supportNullable = Boolean.FALSE;
// nullable type
protected Set<String> nullableType = new HashSet<String>();
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCSharpCodegen.class);
public AbstractCSharpCodegen() {
@@ -130,19 +136,26 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
"String",
"string",
"bool?",
"bool",
"double?",
"double",
"decimal?",
"decimal",
"int?",
"int",
"long?",
"long",
"float?",
"float",
"byte[]",
"ICollection",
"Collection",
"List",
"Dictionary",
"DateTime?",
"DateTime",
"DateTimeOffset?",
"String",
"DataTimeOffset",
"Boolean",
"Double",
"Int32",
@@ -157,6 +170,7 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
instantiationTypes.put("list", "List");
instantiationTypes.put("map", "Dictionary");
// Nullable types here assume C# 2 support is not part of base
typeMapping = new HashMap<String, String>();
typeMapping.put("string", "string");
@@ -176,6 +190,11 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
typeMapping.put("map", "Dictionary");
typeMapping.put("object", "Object");
typeMapping.put("UUID", "Guid?");
// nullable type
nullableType = new HashSet<String>(
Arrays.asList("decimal", "bool", "int", "float", "long", "double", "DateTime", "Guid")
);
}
public void setReturnICollection(boolean returnICollection) {
@@ -209,8 +228,7 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
this.useDateTimeOffsetFlag = flag;
if (flag) {
typeMapping.put("DateTime", "DateTimeOffset?");
}
else {
} else {
typeMapping.put("DateTime", "DateTime?");
}
}
@@ -421,8 +439,8 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
}
for (Map.Entry<String, Object> entry : models.entrySet()) {
String swaggerName = entry.getKey();
CodegenModel model = ModelUtils.getModelByName(swaggerName, models);
String openAPIName = entry.getKey();
CodegenModel model = ModelUtils.getModelByName(openAPIName, models);
if (model != null) {
for (CodegenProperty var : model.allVars) {
if (enumRefs.containsKey(var.dataType)) {
@@ -483,7 +501,7 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
}
}
} else {
LOGGER.warn("Expected to retrieve model %s by name, but no model was found. Check your -Dmodels inclusions.", swaggerName);
LOGGER.warn("Expected to retrieve model %s by name, but no model was found. Check your -Dmodels inclusions.", openAPIName);
}
}
}
@@ -573,28 +591,30 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
}
}
for (CodegenParameter parameter: operation.allParams) {
CodegenModel model = null;
for(Object modelHashMap: allModels) {
CodegenModel codegenModel = ((HashMap<String, CodegenModel>) modelHashMap).get("model");
if (codegenModel.getClassname().equals(parameter.dataType)) {
model = codegenModel;
break;
if (!isSupportNullable()) {
for (CodegenParameter parameter : operation.allParams) {
CodegenModel model = null;
for (Object modelHashMap : allModels) {
CodegenModel codegenModel = ((HashMap<String, CodegenModel>) modelHashMap).get("model");
if (codegenModel.getClassname().equals(parameter.dataType)) {
model = codegenModel;
break;
}
}
}
if (model == null) {
// Primitive data types all come already marked
parameter.isNullable = true;
} else {
// Effectively mark enum models as enums and non-nullable
if (model.isEnum) {
parameter.isEnum = true;
parameter.allowableValues = model.allowableValues;
parameter.isPrimitiveType = true;
parameter.isNullable = false;
} else {
if (model == null) {
// Primitive data types all come already marked
parameter.isNullable = true;
} else {
// Effectively mark enum models as enums and non-nullable
if (model.isEnum) {
parameter.isEnum = true;
parameter.allowableValues = model.allowableValues;
parameter.isPrimitiveType = true;
parameter.isNullable = false;
} else {
parameter.isNullable = true;
}
}
}
}
@@ -792,6 +812,14 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
return reservedWords.contains(word);
}
public String getNullableType(Schema p, String type) {
if (languageSpecificPrimitives.contains(type)) {
return type;
} else {
return null;
}
}
@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
@@ -804,8 +832,9 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
if (typeMapping.containsKey(openAPIType)) {
type = typeMapping.get(openAPIType);
if (languageSpecificPrimitives.contains(type)) {
return type;
String languageType = getNullableType(p, type);
if (languageType != null) {
return languageType;
}
} else {
type = openAPIType;
@@ -950,6 +979,14 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
this.interfacePrefix = interfacePrefix;
}
public boolean isSupportNullable() {
return supportNullable;
}
public void setSupportNullable(final boolean supportNullable) {
this.supportNullable = supportNullable;
}
@Override
public String toEnumValue(String value, String datatype) {
// C# only supports enums as literals for int, int?, long, long?, byte, and byte?. All else must be treated as strings.
@@ -1011,7 +1048,9 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
@Override
public boolean isDataTypeString(String dataType) {
// also treat double/decimal/float as "string" in enum so that the values (e.g. 2.8) get double-quoted
return "String".equalsIgnoreCase(dataType) || "double?".equals(dataType) || "decimal?".equals(dataType) || "float?".equals(dataType);
return "String".equalsIgnoreCase(dataType) ||
"double?".equals(dataType) || "decimal?".equals(dataType) || "float?".equals(dataType) ||
"double".equals(dataType) || "decimal".equals(dataType) || "float".equals(dataType);
}
@Override

View File

@@ -776,7 +776,6 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
}
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}

View File

@@ -22,6 +22,7 @@ import static org.apache.commons.lang3.StringUtils.isEmpty;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.CliOption;
@@ -32,6 +33,7 @@ import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,19 +86,39 @@ public class CSharpRefactorClientCodegen extends AbstractCSharpCodegen {
// By default, generated code is considered public
protected boolean nonPublicApi = Boolean.FALSE;
public CSharpRefactorClientCodegen() {
super();
// mapped non-nullable type without ?
typeMapping = new HashMap<String, String>();
typeMapping.put("string", "string");
typeMapping.put("binary", "byte[]");
typeMapping.put("ByteArray", "byte[]");
typeMapping.put("boolean", "bool");
typeMapping.put("integer", "int");
typeMapping.put("float", "float");
typeMapping.put("long", "long");
typeMapping.put("double", "double");
typeMapping.put("number", "decimal");
typeMapping.put("DateTime", "DateTime");
typeMapping.put("date", "DateTime");
typeMapping.put("file", "System.IO.Stream");
typeMapping.put("array", "List");
typeMapping.put("list", "List");
typeMapping.put("map", "Dictionary");
typeMapping.put("object", "Object");
typeMapping.put("UUID", "Guid");
setSupportNullable(Boolean.TRUE);
hideGenerationTimestamp = Boolean.TRUE;
supportsInheritance = true;
modelTemplateFiles.put("model.mustache", ".cs");
apiTemplateFiles.put("api.mustache", ".cs");
modelDocTemplateFiles.put("model_doc.mustache", ".md");
apiDocTemplateFiles.put("api_doc.mustache", ".md");
embeddedTemplateDir = templateDir = "csharp-refactor";
hideGenerationTimestamp = Boolean.TRUE;
cliOptions.clear();
// CLI options
@@ -223,7 +245,6 @@ public class CSharpRefactorClientCodegen extends AbstractCSharpCodegen {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}
if (isEmpty(apiPackage)) {
setApiPackage("Api");
}
@@ -609,6 +630,10 @@ public class CSharpRefactorClientCodegen extends AbstractCSharpCodegen {
public void postProcessParameter(CodegenParameter parameter) {
postProcessPattern(parameter.pattern, parameter.vendorExtensions);
super.postProcessParameter(parameter);
if (!parameter.required && nullableType.contains(parameter.dataType)) { //optional
parameter.dataType = parameter.dataType + "?";
}
}
@Override
@@ -852,4 +877,17 @@ public class CSharpRefactorClientCodegen extends AbstractCSharpCodegen {
// To avoid unexpected behaviors when options are passed programmatically such as { "supportsAsync": "" }
return super.processCompiler(compiler).emptyStringIsFalse(true);
}
@Override
public String getNullableType(Schema p, String type) {
if (languageSpecificPrimitives.contains(type)) {
if (isSupportNullable() && ModelUtils.isNullable(p) && nullableType.contains(type)) {
return type + "?";
} else {
return type;
}
} else {
return null;
}
}
}