Skip to content

Commit d2bb505

Browse files
committed
Merge pull request #1752 from justusth/master
Csharp Enums
2 parents 1f20265 + 27fc4ba commit d2bb505

File tree

17 files changed

+367
-139
lines changed

17 files changed

+367
-139
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ public static enum MODEL_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case
8787

8888
public static final String MODEL_NAME_SUFFIX = "modelNameSuffix";
8989
public static final String MODEL_NAME_SUFFIX_DESC = "Suffix that will be appended to all model names. Default is the empty string.";
90+
91+
public static final String OPTIONAL_EMIT_DEFAULT_VALUES = "optionalEmitDefaultValues";
92+
public static final String OPTIONAL_EMIT_DEFAULT_VALUES_DESC = "Set DataMember's EmitDefaultValue, default false.";
93+
9094
}

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractCSharpCodegen.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
1313

1414
protected boolean optionalAssemblyInfoFlag = true;
1515
protected boolean optionalProjectFileFlag = false;
16+
protected boolean optionalEmitDefaultValue = false;
1617
protected boolean optionalMethodArgumentFlag = true;
1718
protected boolean useDateTimeOffsetFlag = false;
1819
protected boolean useCollection = false;
@@ -120,6 +121,10 @@ public void setReturnICollection(boolean returnICollection) {
120121
this.returnICollection = returnICollection;
121122
}
122123

124+
public void setOptionalEmitDefaultValue(boolean optionalEmitDefaultValue) {
125+
this.optionalEmitDefaultValue = optionalEmitDefaultValue;
126+
}
127+
123128
public void setUseCollection(boolean useCollection) {
124129
this.useCollection = useCollection;
125130
if (useCollection) {
@@ -191,6 +196,15 @@ public void processOpts() {
191196
if (additionalProperties.containsKey(CodegenConstants.RETURN_ICOLLECTION)) {
192197
setReturnICollection(Boolean.valueOf(additionalProperties.get(CodegenConstants.RETURN_ICOLLECTION).toString()));
193198
}
199+
200+
if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES)) {
201+
setOptionalEmitDefaultValue(Boolean.valueOf(additionalProperties.get(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES).toString()));
202+
}
203+
}
204+
205+
@Override
206+
public String toEnumName(CodegenProperty property) {
207+
return StringUtils.capitalize(property.name) + "Enum?";
194208
}
195209

196210
@Override

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/CSharpClientCodegen.java

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@
55
import io.swagger.codegen.CodegenConfig;
66
import io.swagger.codegen.CodegenConstants;
77
import io.swagger.codegen.CodegenType;
8+
import io.swagger.codegen.CodegenModel;
89
import io.swagger.codegen.DefaultCodegen;
910
import io.swagger.codegen.SupportingFile;
1011
import io.swagger.codegen.CodegenProperty;
1112
import io.swagger.codegen.CodegenModel;
1213
import io.swagger.codegen.CodegenOperation;
1314
import io.swagger.models.properties.*;
1415
import io.swagger.codegen.CliOption;
16+
import io.swagger.models.Model;
1517

1618
import java.io.File;
1719
import java.util.Arrays;
1820
import java.util.HashMap;
1921
import java.util.HashSet;
2022
import java.util.List;
2123
import java.util.Map;
24+
import java.util.ArrayList;
25+
import java.util.Iterator;
2226

2327
import org.apache.commons.lang.StringUtils;
28+
import org.apache.commons.lang.WordUtils;
2429
import org.slf4j.Logger;
2530
import org.slf4j.LoggerFactory;
2631

@@ -37,6 +42,7 @@ public class CSharpClientCodegen extends AbstractCSharpCodegen {
3742
protected String packageCompany = "Swagger";
3843
protected String packageCopyright = "No Copyright";
3944
protected String clientPackage = "IO.Swagger.Client";
45+
protected String localVariablePrefix = "";
4046

4147
protected String targetFramework = NET45;
4248
protected String targetFrameworkNuget = "net45";
@@ -115,6 +121,10 @@ public CSharpClientCodegen() {
115121
addSwitch(CodegenConstants.OPTIONAL_PROJECT_FILE,
116122
CodegenConstants.OPTIONAL_PROJECT_FILE_DESC,
117123
this.optionalProjectFileFlag);
124+
125+
addSwitch(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES,
126+
CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES_DESC,
127+
this.optionalEmitDefaultValue);
118128
}
119129

120130
@Override
@@ -133,6 +143,7 @@ public void processOpts() {
133143
additionalProperties.put("packageDescription", packageDescription);
134144
additionalProperties.put("packageCompany", packageCompany);
135145
additionalProperties.put("packageCopyright", packageCopyright);
146+
additionalProperties.put("emitDefaultValue", optionalEmitDefaultValue);
136147

137148
if (additionalProperties.containsKey(CodegenConstants.DOTNET_FRAMEWORK)) {
138149
setTargetFramework((String) additionalProperties.get(CodegenConstants.DOTNET_FRAMEWORK));
@@ -253,6 +264,18 @@ public void setOptionalAssemblyInfoFlag(boolean flag) {
253264
this.optionalAssemblyInfoFlag = flag;
254265
}
255266

267+
@Override
268+
public CodegenModel fromModel(String name, Model model, Map<String, Model> allDefinitions) {
269+
CodegenModel codegenModel = super.fromModel(name, model, allDefinitions);
270+
if (allDefinitions != null && codegenModel != null && codegenModel.parent != null && codegenModel.hasEnums) {
271+
final Model parentModel = allDefinitions.get(toModelName(codegenModel.parent));
272+
final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel);
273+
codegenModel = this.reconcileInlineEnums(codegenModel, parentCodegenModel);
274+
}
275+
276+
return codegenModel;
277+
}
278+
256279
public void setOptionalProjectFileFlag(boolean flag) {
257280
this.optionalProjectFileFlag = flag;
258281
}
@@ -261,6 +284,73 @@ public void setPackageGuid(String packageGuid) {
261284
this.packageGuid = packageGuid;
262285
}
263286

287+
@Override
288+
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
289+
List<Object> models = (List<Object>) objs.get("models");
290+
for (Object _mo : models) {
291+
Map<String, Object> mo = (Map<String, Object>) _mo;
292+
CodegenModel cm = (CodegenModel) mo.get("model");
293+
for (CodegenProperty var : cm.vars) {
294+
Map<String, Object> allowableValues = var.allowableValues;
295+
296+
// handle ArrayProperty
297+
if (var.items != null) {
298+
allowableValues = var.items.allowableValues;
299+
}
300+
301+
if (allowableValues == null) {
302+
continue;
303+
}
304+
List<String> values = (List<String>) allowableValues.get("values");
305+
if (values == null) {
306+
continue;
307+
}
308+
309+
// put "enumVars" map into `allowableValues", including `name` and `value`
310+
List<Map<String, String>> enumVars = new ArrayList<Map<String, String>>();
311+
String commonPrefix = findCommonPrefixOfVars(values);
312+
int truncateIdx = commonPrefix.length();
313+
for (String value : values) {
314+
Map<String, String> enumVar = new HashMap<String, String>();
315+
String enumName;
316+
if (truncateIdx == 0) {
317+
enumName = value;
318+
} else {
319+
enumName = value.substring(truncateIdx);
320+
if ("".equals(enumName)) {
321+
enumName = value;
322+
}
323+
}
324+
enumVar.put("name", toEnumVarName(enumName));
325+
enumVar.put("jsonname", value);
326+
enumVar.put("value", value);
327+
enumVars.add(enumVar);
328+
}
329+
allowableValues.put("enumVars", enumVars);
330+
// handle default value for enum, e.g. available => StatusEnum.AVAILABLE
331+
if (var.defaultValue != null) {
332+
String enumName = null;
333+
for (Map<String, String> enumVar : enumVars) {
334+
if (var.defaultValue.equals(enumVar.get("value"))) {
335+
enumName = enumVar.get("name");
336+
break;
337+
}
338+
}
339+
if (enumName != null) {
340+
var.defaultValue = var.datatypeWithEnum + "." + enumName;
341+
}
342+
}
343+
344+
// HACK: strip ? from enum
345+
if (var.datatypeWithEnum != null) {
346+
var.vendorExtensions.put("plainDatatypeWithEnum", var.datatypeWithEnum.substring(0, var.datatypeWithEnum.length() - 1));
347+
}
348+
}
349+
}
350+
351+
return objs;
352+
}
353+
264354
public void setTargetFramework(String dotnetFramework) {
265355
if(!frameworks.containsKey(dotnetFramework)){
266356
LOGGER.warn("Invalid .NET framework version, defaulting to " + this.targetFramework);
@@ -270,6 +360,82 @@ public void setTargetFramework(String dotnetFramework) {
270360
LOGGER.info("Generating code for .NET Framework " + this.targetFramework);
271361
}
272362

363+
private CodegenModel reconcileInlineEnums(CodegenModel codegenModel, CodegenModel parentCodegenModel) {
364+
// This generator uses inline classes to define enums, which breaks when
365+
// dealing with models that have subTypes. To clean this up, we will analyze
366+
// the parent and child models, look for enums that match, and remove
367+
// them from the child models and leave them in the parent.
368+
// Because the child models extend the parents, the enums will be available via the parent.
369+
370+
// Only bother with reconciliation if the parent model has enums.
371+
if (parentCodegenModel.hasEnums) {
372+
373+
// Get the properties for the parent and child models
374+
final List<CodegenProperty> parentModelCodegenProperties = parentCodegenModel.vars;
375+
List<CodegenProperty> codegenProperties = codegenModel.vars;
376+
377+
// Iterate over all of the parent model properties
378+
boolean removedChildEnum = false;
379+
for (CodegenProperty parentModelCodegenPropery : parentModelCodegenProperties) {
380+
// Look for enums
381+
if (parentModelCodegenPropery.isEnum) {
382+
// Now that we have found an enum in the parent class,
383+
// and search the child class for the same enum.
384+
Iterator<CodegenProperty> iterator = codegenProperties.iterator();
385+
while (iterator.hasNext()) {
386+
CodegenProperty codegenProperty = iterator.next();
387+
if (codegenProperty.isEnum && codegenProperty.equals(parentModelCodegenPropery)) {
388+
// We found an enum in the child class that is
389+
// a duplicate of the one in the parent, so remove it.
390+
iterator.remove();
391+
removedChildEnum = true;
392+
}
393+
}
394+
}
395+
}
396+
397+
if(removedChildEnum) {
398+
// If we removed an entry from this model's vars, we need to ensure hasMore is updated
399+
int count = 0, numVars = codegenProperties.size();
400+
for(CodegenProperty codegenProperty : codegenProperties) {
401+
count += 1;
402+
codegenProperty.hasMore = (count < numVars) ? true : null;
403+
}
404+
codegenModel.vars = codegenProperties;
405+
}
406+
}
407+
408+
return codegenModel;
409+
}
410+
411+
private String findCommonPrefixOfVars(List<String> vars) {
412+
String prefix = StringUtils.getCommonPrefix(vars.toArray(new String[vars.size()]));
413+
// exclude trailing characters that should be part of a valid variable
414+
// e.g. ["status-on", "status-off"] => "status-" (not "status-o")
415+
return prefix.replaceAll("[a-zA-Z0-9]+\\z", "");
416+
}
417+
418+
private String toEnumVarName(String value) {
419+
String var = value.replaceAll("_", " ");
420+
var = WordUtils.capitalizeFully(var);
421+
var = var.replaceAll("\\W+", "");
422+
423+
if (var.matches("\\d.*")) {
424+
return "_" + var;
425+
} else {
426+
return var;
427+
}
428+
}
429+
430+
431+
public void setPackageName(String packageName) {
432+
this.packageName = packageName;
433+
}
434+
435+
public void setPackageVersion(String packageVersion) {
436+
this.packageVersion = packageVersion;
437+
}
438+
273439
public void setTargetFrameworkNuget(String targetFrameworkNuget) {
274440
this.targetFrameworkNuget = targetFrameworkNuget;
275441
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public enum {{vendorExtensions.plainDatatypeWithEnum}} {
2+
{{#allowableValues}}{{#enumVars}}
3+
[EnumMember(Value = "{{jsonname}}")]
4+
{{name}}{{^-last}},
5+
{{/-last}}{{#-last}}{{/-last}}{{/enumVars}}{{/allowableValues}}
6+
}

modules/swagger-codegen/src/main/resources/csharp/model.mustache

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,37 @@ using System.Collections.Generic;
77
using System.Collections.ObjectModel;
88
using System.Runtime.Serialization;
99
using Newtonsoft.Json;
10+
using Newtonsoft.Json.Converters;
1011

1112
{{#models}}
1213
{{#model}}
1314
namespace {{packageName}}.Model
1415
{
15-
1616
/// <summary>
1717
/// {{description}}
1818
/// </summary>
1919
[DataContract]
2020
public partial class {{classname}} : {{#parent}}{{{parent}}}, {{/parent}} IEquatable<{{classname}}>
21-
{
21+
{ {{#vars}}{{#isEnum}}
22+
23+
[JsonConverter(typeof(StringEnumConverter))]
24+
{{>enumClass}}{{/isEnum}}{{#items.isEnum}}{{#items}}
25+
{{>enumClass}}{{/items}}{{/items.isEnum}}{{/vars}}
26+
{{#vars}}{{#isEnum}}
27+
/// <summary>
28+
/// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{{description}}}{{/description}}
29+
/// </summary>{{#description}}
30+
/// <value>{{{description}}}</value>{{/description}}
31+
[DataMember(Name="{{baseName}}", EmitDefaultValue={{emitDefaultValue}})]
32+
public {{{datatypeWithEnum}}} {{name}} { get; set; }
33+
{{/isEnum}}{{/vars}}
2234
/// <summary>
35+
/// Initializes a new instance of the <see cref="{{classname}}" /> class.
2336
/// Initializes a new instance of the <see cref="{{classname}}" />class.
2437
/// </summary>
2538
{{#vars}}{{^isReadOnly}} /// <param name="{{name}}">{{#description}}{{description}}{{/description}}{{^description}}{{name}}{{/description}}{{#required}} (required){{/required}}{{#defaultValue}} (default to {{defaultValue}}){{/defaultValue}}.</param>
2639
{{/isReadOnly}}{{/vars}}
27-
public {{classname}}({{#vars}}{{^isReadOnly}}{{{datatype}}} {{name}} = null{{#hasMore}}, {{/hasMore}}{{/isReadOnly}}{{/vars}})
40+
public {{classname}}({{#vars}}{{^isReadOnly}}{{{datatypeWithEnum}}} {{name}} = null{{#hasMore}}, {{/hasMore}}{{/isReadOnly}}{{/vars}})
2841
{
2942
{{#vars}}{{^isReadOnly}}{{#required}}// to ensure "{{name}}" is required (not null)
3043
if ({{name}} == null)
@@ -47,17 +60,15 @@ namespace {{packageName}}.Model
4760
{{/defaultValue}}{{^defaultValue}}this.{{name}} = {{name}};
4861
{{/defaultValue}}{{/required}}{{/isReadOnly}}{{/vars}}
4962
}
50-
51-
{{#vars}}
63+
64+
{{#vars}}{{^isEnum}}
5265
/// <summary>
5366
/// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{{description}}}{{/description}}
5467
/// </summary>{{#description}}
5568
/// <value>{{{description}}}</value>{{/description}}
56-
[DataMember(Name="{{baseName}}", EmitDefaultValue=false)]
69+
[DataMember(Name="{{baseName}}", EmitDefaultValue={{emitDefaultValue}})]
5770
public {{{datatype}}} {{name}} { get; {{#isReadOnly}}private {{/isReadOnly}}set; }
58-
59-
{{/vars}}
60-
71+
{{/isEnum}}{{/vars}}
6172
/// <summary>
6273
/// Returns the string presentation of the object
6374
/// </summary>

modules/swagger-codegen/src/test/java/io/swagger/codegen/csharp/CSharpClientOptionsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ protected void setExpectations() {
4646
times = 1;
4747
clientCodegen.setReturnICollection(false);
4848
times = 1;
49+
clientCodegen.setOptionalEmitDefaultValue(true);
50+
times = 1;
4951
}};
5052
}
5153
}

modules/swagger-codegen/src/test/java/io/swagger/codegen/options/CSharpClientOptionsProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public Map<String, String> createOptions() {
3232
.put(CodegenConstants.OPTIONAL_PROJECT_FILE, "true")
3333
.put(CodegenConstants.OPTIONAL_PROJECT_GUID, PACKAGE_GUID_VALUE)
3434
.put(CodegenConstants.DOTNET_FRAMEWORK, "4.x")
35+
.put(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES, "true")
3536
.build();
3637
}
3738

0 commit comments

Comments
 (0)