Skip to content

Commit 0e34bcf

Browse files
jimschubertwing328
authored andcommitted
[csharp] ctor params should always be camelCase (#7519)
* [csharp] ctor params should always be camelCase After PR #6305, var names defaulted to PascalCase results in constructor arguments also being PacalCase. Model properties and constructor arguments have no reason to be the same case, and in fact may cause issues (`name = name` will result in a compilation error). This commit forces all constructor params in models to lowerCase. This is a necessary change, for instance, if client SDK consumers assign using named args: var a = new Model(first = "", second = "") The PacalCase default and update to constructor arg casing will break existing consumers of the client. See #7070 for more details and discussion. * [csharp] Regenerate samples * [csharp] Remove client models generated from a different spec. * [csharp] Escape reserved words on camelcase/lowercase lambdas * [csharp] Regenerate samples
1 parent 1139f3f commit 0e34bcf

File tree

159 files changed

+1368
-1383
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+1368
-1383
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,6 @@ public interface CodegenConfig {
216216

217217
String toGetter(String name);
218218

219+
String sanitizeName(String name);
220+
219221
}

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package io.swagger.codegen.languages;
22

3+
import com.google.common.collect.ImmutableMap;
4+
import com.samskivert.mustache.Mustache;
35
import io.swagger.codegen.*;
6+
import io.swagger.codegen.mustache.*;
47
import io.swagger.codegen.utils.ModelUtils;
58
import io.swagger.models.properties.*;
69
import org.apache.commons.lang3.StringUtils;
@@ -21,7 +24,7 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen implements Co
2124
protected boolean returnICollection = false;
2225
protected boolean netCoreProjectFileFlag = false;
2326

24-
protected String modelPropertyNaming = "PascalCase";
27+
protected String modelPropertyNaming = CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.PascalCase.name();
2528

2629
protected String packageVersion = "1.0.0";
2730
protected String packageName = "IO.Swagger";
@@ -305,6 +308,32 @@ public void processOpts() {
305308

306309
// This either updates additionalProperties with the above fixes, or sets the default if the option was not specified.
307310
additionalProperties.put(CodegenConstants.INTERFACE_PREFIX, interfacePrefix);
311+
312+
addMustacheLambdas(additionalProperties);
313+
}
314+
315+
private void addMustacheLambdas(Map<String, Object> objs) {
316+
317+
Map<String, Mustache.Lambda> lambdas = new ImmutableMap.Builder<String, Mustache.Lambda>()
318+
.put("lowercase", new LowercaseLambda().generator(this))
319+
.put("uppercase", new UppercaseLambda())
320+
.put("titlecase", new TitlecaseLambda())
321+
.put("camelcase", new CamelCaseLambda().generator(this))
322+
.put("camelcase_param", new CamelCaseLambda().generator(this).escapeAsParamName(true))
323+
.put("indented", new IndentedLambda())
324+
.put("indented_8", new IndentedLambda(8, " "))
325+
.put("indented_12", new IndentedLambda(12, " "))
326+
.put("indented_16", new IndentedLambda(16, " "))
327+
.build();
328+
329+
if (objs.containsKey("lambda")) {
330+
LOGGER.warn("An property named 'lambda' already exists. Mustache lambdas renamed from 'lambda' to '_lambda'. " +
331+
"You'll likely need to use a custom template, " +
332+
"see https://github.com/swagger-api/swagger-codegen#modifying-the-client-library-format. ");
333+
objs.put("_lambda", lambdas);
334+
} else {
335+
objs.put("lambda", lambdas);
336+
}
308337
}
309338

310339
@Override
@@ -351,7 +380,7 @@ public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
351380
* When working with enums, we can't always assume a RefModel is a nullable type (where default(YourType) == null),
352381
* so this post processing runs through all models to find RefModel'd enums. Then, it runs through all vars and modifies
353382
* those vars referencing RefModel'd enums to work the same as inlined enums rather than as objects.
354-
* @param models
383+
* @param models processed models to be further processed for enum references
355384
*/
356385
@SuppressWarnings({ "unchecked" })
357386
private void postProcessEnumRefs(final Map<String, Object> models) {
@@ -750,8 +779,8 @@ public String getSwaggerType(Property p) {
750779

751780
/**
752781
* Provides C# strongly typed declaration for simple arrays of some type and arrays of arrays of some type.
753-
* @param arr
754-
* @return
782+
* @param arr The input array property
783+
* @return The type declaration when the type is an array of arrays.
755784
*/
756785
private String getArrayTypeDeclaration(ArrayProperty arr) {
757786
// TODO: collection type here should be fully qualified namespace to avoid model conflicts

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

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,19 @@
22

33
import com.google.common.collect.ImmutableMap;
44
import com.samskivert.mustache.Mustache;
5-
import com.samskivert.mustache.Template;
6-
import io.swagger.codegen.*;
7-
8-
import java.io.File;
9-
import java.io.IOException;
10-
import java.io.Writer;
11-
import java.util.*;
12-
13-
import io.swagger.codegen.mustache.IndentedLambda;
14-
import io.swagger.codegen.mustache.LowercaseLambda;
15-
import io.swagger.codegen.mustache.TitlecaseLambda;
16-
import io.swagger.codegen.mustache.UppercaseLambda;
17-
import org.apache.commons.lang3.StringUtils;
5+
import io.swagger.codegen.CliOption;
6+
import io.swagger.codegen.CodegenConstants;
7+
import io.swagger.codegen.CodegenType;
8+
import io.swagger.codegen.SupportingFile;
9+
import io.swagger.codegen.mustache.*;
1810
import org.slf4j.Logger;
1911
import org.slf4j.LoggerFactory;
2012

13+
import java.io.File;
14+
import java.util.Arrays;
15+
import java.util.List;
16+
import java.util.Map;
17+
2118
public class KotlinServerCodegen extends AbstractKotlinCodegen {
2219

2320
public static final String DEFAULT_LIBRARY = Constants.KTOR;
@@ -191,9 +188,10 @@ public void processOpts() {
191188
private void addMustacheLambdas(Map<String, Object> objs) {
192189

193190
Map<String, Mustache.Lambda> lambdas = new ImmutableMap.Builder<String, Mustache.Lambda>()
194-
.put("lowercase", new LowercaseLambda())
191+
.put("lowercase", new LowercaseLambda().generator(this))
195192
.put("uppercase", new UppercaseLambda())
196193
.put("titlecase", new TitlecaseLambda())
194+
.put("camelcase", new CamelCaseLambda().generator(this))
197195
.put("indented", new IndentedLambda())
198196
.put("indented_8", new IndentedLambda(8, " "))
199197
.put("indented_12", new IndentedLambda(12, " "))
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.swagger.codegen.mustache;
2+
3+
import com.google.common.base.CaseFormat;
4+
import com.samskivert.mustache.Mustache;
5+
import com.samskivert.mustache.Template;
6+
import io.swagger.codegen.CodegenConfig;
7+
import io.swagger.codegen.DefaultCodegen;
8+
import org.apache.commons.lang3.StringUtils;
9+
import org.apache.commons.lang3.text.WordUtils;
10+
11+
import java.io.IOException;
12+
import java.io.Writer;
13+
14+
/**
15+
* Converts text in a fragment to camelCase.
16+
*
17+
* Register:
18+
* <pre>
19+
* additionalProperties.put("camelcase", new CamelCaseLambda());
20+
* </pre>
21+
*
22+
* Use:
23+
* <pre>
24+
* {{#camelcase}}{{name}}{{/camelcase}}
25+
* </pre>
26+
*/
27+
public class CamelCaseLambda implements Mustache.Lambda {
28+
private CodegenConfig generator = null;
29+
private Boolean escapeParam = false;
30+
31+
public CamelCaseLambda() {
32+
33+
}
34+
35+
public CamelCaseLambda generator(final CodegenConfig generator) {
36+
this.generator = generator;
37+
return this;
38+
}
39+
40+
public CamelCaseLambda escapeAsParamName(final Boolean escape) {
41+
this.escapeParam = escape;
42+
return this;
43+
}
44+
45+
@Override
46+
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
47+
String text = DefaultCodegen.camelize(fragment.execute(), true);
48+
if (generator != null) {
49+
text = generator.sanitizeName(text);
50+
if (generator.reservedWords().contains(text)) {
51+
// Escaping must be done *after* camelize, because generators may escape using characters removed by camelize function.
52+
text = generator.escapeReservedWord(text);
53+
}
54+
55+
if (escapeParam) {
56+
// NOTE: many generators call escapeReservedWord in toParamName, but we can't assume that's always the case.
57+
// Here, we'll have to accept that we may be duplicating some work.
58+
text = generator.toParamName(text);
59+
}
60+
}
61+
writer.write(text);
62+
}
63+
}

modules/swagger-codegen/src/main/java/io/swagger/codegen/mustache/LowercaseLambda.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.samskivert.mustache.Mustache;
44
import com.samskivert.mustache.Template;
5+
import io.swagger.codegen.CodegenConfig;
56

67
import java.io.IOException;
78
import java.io.Writer;
@@ -20,10 +21,24 @@
2021
* </pre>
2122
*/
2223
public class LowercaseLambda implements Mustache.Lambda {
24+
private CodegenConfig generator = null;
25+
26+
public LowercaseLambda() {
27+
28+
}
29+
30+
public LowercaseLambda generator(final CodegenConfig generator) {
31+
this.generator = generator;
32+
return this;
33+
}
34+
2335
@Override
2436
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
25-
String text = fragment.execute();
26-
writer.write(text.toLowerCase());
37+
String text = fragment.execute().toLowerCase();
38+
if (generator != null && generator.reservedWords().contains(text)) {
39+
text = generator.escapeReservedWord(text);
40+
}
41+
writer.write(text);
2742

2843
}
2944
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,26 +49,26 @@
4949
/// </summary>
5050
{{#vars}}
5151
{{^isReadOnly}}
52-
/// <param name="{{name}}">{{#description}}{{description}}{{/description}}{{^description}}{{name}}{{/description}}{{#required}} (required){{/required}}{{#defaultValue}} (default to {{defaultValue}}){{/defaultValue}}.</param>
52+
/// <param name="{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}">{{#description}}{{description}}{{/description}}{{^description}}{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}{{/description}}{{#required}} (required){{/required}}{{#defaultValue}} (default to {{defaultValue}}){{/defaultValue}}.</param>
5353
{{/isReadOnly}}
5454
{{/vars}}
5555
{{#hasOnlyReadOnly}}
5656
[JsonConstructorAttribute]
5757
{{/hasOnlyReadOnly}}
58-
public {{classname}}({{#readWriteVars}}{{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}} {{name}} = {{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}default({{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^-last}}, {{/-last}}{{/readWriteVars}}){{#parent}} : base({{#parentVars}}{{name}}{{#hasMore}}, {{/hasMore}}{{/parentVars}}){{/parent}}
58+
public {{classname}}({{#readWriteVars}}{{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}} {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} = {{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}default({{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}{{^required}}?{{/required}}{{/isContainer}}{{/isEnum}}){{/defaultValue}}{{^-last}}, {{/-last}}{{/readWriteVars}}){{#parent}} : base({{#parentVars}}{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}{{#hasMore}}, {{/hasMore}}{{/parentVars}}){{/parent}}
5959
{
6060
{{#vars}}
6161
{{^isInherited}}
6262
{{^isReadOnly}}
6363
{{#required}}
64-
// to ensure "{{name}}" is required (not null)
65-
if ({{name}} == null)
64+
// to ensure "{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}" is required (not null)
65+
if ({{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} == null)
6666
{
67-
throw new InvalidDataException("{{name}} is a required property for {{classname}} and cannot be null");
67+
throw new InvalidDataException("{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} is a required property for {{classname}} and cannot be null");
6868
}
6969
else
7070
{
71-
this.{{name}} = {{name}};
71+
this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
7272
}
7373
{{/required}}
7474
{{/isReadOnly}}
@@ -78,18 +78,18 @@
7878
{{^isInherited}}
7979
{{^isReadOnly}}
8080
{{^required}}
81-
{{#defaultValue}}// use default value if no "{{name}}" provided
82-
if ({{name}} == null)
81+
{{#defaultValue}}// use default value if no "{{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}}" provided
82+
if ({{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}} == null)
8383
{
8484
this.{{name}} = {{{defaultValue}}};
8585
}
8686
else
8787
{
88-
this.{{name}} = {{name}};
88+
this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
8989
}
9090
{{/defaultValue}}
9191
{{^defaultValue}}
92-
this.{{name}} = {{name}};
92+
this.{{name}} = {{#lambda.camelcase_param}}{{name}}{{/lambda.camelcase_param}};
9393
{{/defaultValue}}
9494
{{/required}}
9595
{{/isReadOnly}}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package io.swagger.codegen.mustache;
2+
3+
import io.swagger.codegen.CodegenConfig;
4+
import io.swagger.codegen.languages.CSharpClientCodegen;
5+
import io.swagger.codegen.languages.ScalaClientCodegen;
6+
import org.testng.annotations.Factory;
7+
import org.testng.annotations.Test;
8+
9+
import static org.testng.Assert.*;
10+
11+
public class CamelCaseLambdaTest extends MustacheTestBase {
12+
private final String input;
13+
private final String expected;
14+
private Boolean escapeAsParamName = false;
15+
16+
private CodegenConfig generator = null;
17+
18+
public CamelCaseLambdaTest(String input, String expected) {
19+
this.input = input;
20+
this.expected = expected;
21+
}
22+
23+
public CamelCaseLambdaTest generator(CodegenConfig generator) {
24+
this.generator = generator;
25+
return this;
26+
}
27+
28+
public CamelCaseLambdaTest escapeAsParamName(Boolean setting) {
29+
this.escapeAsParamName = setting;
30+
return this;
31+
}
32+
33+
@Test(description = "camelCase expected inputs")
34+
public void testExecute() throws Exception {
35+
// Arrange
36+
String template = "{{#camelcase}}{{value}}{{/camelcase}}";
37+
Object inputCtx = context(
38+
"camelcase", new CamelCaseLambda().generator(this.generator).escapeAsParamName(this.escapeAsParamName),
39+
"value", this.input
40+
);
41+
42+
// Act
43+
String actual = compile(template, inputCtx);
44+
45+
46+
// Assert
47+
assertEquals(actual, this.expected);
48+
}
49+
50+
@Factory
51+
public static Object[] factoryMethod() {
52+
return new Object[] {
53+
new CamelCaseLambdaTest("lowercase input", "lowercase input"),
54+
55+
// NOTE: DefaultCodegen.camelize(string, true) only results in first character of first word being lowercased.
56+
// Keeping this behavior as it will match whatever is expected by existing codegen implementations.
57+
new CamelCaseLambdaTest("UPPERCASE INPUT", "uPPERCASE INPUT"),
58+
new CamelCaseLambdaTest("inputText", "inputText"),
59+
new CamelCaseLambdaTest("input_text", "inputText"),
60+
61+
// TODO: This result for INPUT_TEXT may be unexpected, but is the result of DefaultCodegen.camelize.
62+
// CamelCaseLambda can be extended to accept a method reference after move to Java 8.
63+
new CamelCaseLambdaTest("INPUT_TEXT", "iNPUTTEXT"),
64+
new CamelCaseLambdaTest("input-text", "inputText"),
65+
new CamelCaseLambdaTest("input-text input-text input-text input-text input-text", "inputText inputText inputText inputText inputText"),
66+
// C# codegen at time of writing this test escapes using a character that would be removed by camelize function.
67+
new CamelCaseLambdaTest("class", "_class").generator(new CSharpClientCodegen()),
68+
new CamelCaseLambdaTest("123List", "_123List").generator(new CSharpClientCodegen()).escapeAsParamName(true),
69+
// Scala codegen is only one at time of writing this test that uses a Mustache.Escaper
70+
new CamelCaseLambdaTest("class", "`class`").generator(new ScalaClientCodegen())
71+
};
72+
}
73+
}

modules/swagger-codegen/src/test/java/io/swagger/codegen/mustache/LowercaseLambdaTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.swagger.codegen.mustache;
22

3+
import io.swagger.codegen.languages.CSharpClientCodegen;
34
import org.testng.annotations.Test;
45

56
import static org.testng.Assert.*;
@@ -28,4 +29,21 @@ public void testExecute() throws Exception {
2829
assertEquals(lowercaseResult, "lowercase input");
2930
assertEquals(uppercaseResult, "uppercase input");
3031
}
32+
33+
@Test(description = "lowercase escapes reserved words")
34+
public void testEscapingReservedWords() {
35+
// Arrange
36+
String template = "{{#lowercase}}{{value}}{{/lowercase}}";
37+
String expected = "_class";
38+
Object ctx = context(
39+
"lowercase", new LowercaseLambda().generator(new CSharpClientCodegen()),
40+
"value", "CLASS"
41+
);
42+
43+
// Act
44+
String actual = compile(template, ctx);
45+
46+
// Assert
47+
assertEquals(actual, expected);
48+
}
3149
}

0 commit comments

Comments
 (0)