Skip to content

Commit 75c0180

Browse files
jimschubertwing328
authored andcommitted
[scala] Escape reserved words, support Array[Byte] (#7378)
* [scala] Escape reserved words, support Array[Byte] Previously, Array[Byte] was compiling to ArrayByte. This provides a type mapping to output the correct type. This also escapes reserved words with grave accents, as is most common in Scala. Escaping with an underscore prefix breaks serialization (in Jackson, for example) unless templates are modified manually. Escaping using grave accent should unblock most serializers from requiring template modifications. * [scala] Regenerate integration test outputs * [scala] Regenerate samples * [scala] Remove unused imports in related codegen files
1 parent acf70d0 commit 75c0180

File tree

7 files changed

+96
-12
lines changed

7 files changed

+96
-12
lines changed

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

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.util.List;
77
import java.util.Map;
88

9+
import com.samskivert.mustache.Escapers;
10+
import com.samskivert.mustache.Mustache;
911
import io.swagger.codegen.CliOption;
1012
import io.swagger.codegen.CodegenConstants;
1113
import io.swagger.codegen.DefaultCodegen;
@@ -43,7 +45,50 @@ public AbstractScalaCodegen() {
4345
"Any",
4446
"List",
4547
"Seq",
46-
"Map"));
48+
"Map",
49+
"Array"));
50+
51+
reservedWords.addAll(Arrays.asList(
52+
"abstract",
53+
"case",
54+
"catch",
55+
"class",
56+
"def",
57+
"do",
58+
"else",
59+
"extends",
60+
"false",
61+
"final",
62+
"finally",
63+
"for",
64+
"forSome",
65+
"if",
66+
"implicit",
67+
"import",
68+
"lazy",
69+
"match",
70+
"new",
71+
"null",
72+
"object",
73+
"override",
74+
"package",
75+
"private",
76+
"protected",
77+
"return",
78+
"sealed",
79+
"super",
80+
"this",
81+
"throw",
82+
"trait",
83+
"try",
84+
"true",
85+
"type",
86+
"val",
87+
"var",
88+
"while",
89+
"with",
90+
"yield"
91+
));
4792

4893
cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC));
4994
cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC));
@@ -72,7 +117,30 @@ public String escapeReservedWord(String name) {
72117
if (this.reservedWordsMappings().containsKey(name)) {
73118
return this.reservedWordsMappings().get(name);
74119
}
75-
return "_" + name;
120+
// Reserved words will be further escaped at the mustache compiler level.
121+
// Scala escaping done here (via `, without compiler escaping) would otherwise be HTML encoded.
122+
return "`" + name + "`";
123+
}
124+
125+
@Override
126+
public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
127+
Mustache.Escaper SCALA = new Mustache.Escaper() {
128+
@Override public String escape (String text) {
129+
// Fix included as suggested by akkie in #6393
130+
// The given text is a reserved word which is escaped by enclosing it with grave accents. If we would
131+
// escape that with the default Mustache `HTML` escaper, then the escaper would also escape our grave
132+
// accents. So we remove the grave accents before the escaping and add it back after the escaping.
133+
if (text.startsWith("`") && text.endsWith("`")) {
134+
String unescaped = text.substring(1, text.length() - 1);
135+
return "`" + Escapers.HTML.escape(unescaped) + "`";
136+
}
137+
138+
// All none reserved words will be escaped with the default Mustache `HTML` escaper
139+
return Escapers.HTML.escape(text);
140+
}
141+
};
142+
143+
return compiler.withEscaper(SCALA);
76144
}
77145

78146
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.io.File;
66
import java.util.Arrays;
77
import java.util.HashMap;
8-
import java.util.Map;
98

109
import org.apache.commons.lang3.StringUtils;
1110

@@ -96,6 +95,7 @@ public ScalaClientCodegen() {
9695
typeMapping.put("file", "File");
9796
typeMapping.put("binary", "Array[Byte]");
9897
typeMapping.put("ByteArray", "Array[Byte]");
98+
typeMapping.put("ArrayByte", "Array[Byte]");
9999
typeMapping.put("date-time", "Date");
100100
typeMapping.put("DateTime", "Date");
101101

modules/swagger-codegen/src/test/java/io/swagger/codegen/languages/AbstractScalaCodegenTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public void shouldEscapeReservedWords() {
3737

3838
String result = abstractScalaCodegen.formatIdentifier(className, true);
3939

40-
Assert.assertTrue("_ReservedWord".equals(result));
40+
// NOTE: reserved words are further escaped at the compiler level.
41+
Assert.assertTrue("`ReservedWord`".equals(result));
4142
}
4243

4344
@Test

modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/HobbiesApi.scala

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class HobbiesApi(
8686
* Query hobbies with some additional optional meaningless parameters
8787
*
8888
* @param s a string (optional, default to some string)
89+
* @param `class` a string, testing keyword escaping (optional, default to some string)
8990
* @param i an integer (optional, default to 1)
9091
* @param l a long (optional, default to 2)
9192
* @param bool a bool (optional, default to true)
@@ -97,8 +98,8 @@ class HobbiesApi(
9798
* @param bin an octet string (optional, default to DEADBEEF)
9899
* @return Hobby
99100
*/
100-
def getHobbies(s: Option[String] = Option("some string"), i: Option[Integer] = Option(1), l: Option[Long] = Option(2), bool: Option[Boolean] = Option(true), f: Option[Float] = Option(0.1), d: Option[Double] = Option(10.005), datetime: Option[Date] = Option(dateTimeFormatter.parse("2018-01-01T08:30:00Z-04:00")), date: Option[Date] = Option(dateFormatter.parse("2018-01-01")), b: Option[ArrayByte] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes), bin: Option[ArrayByte] = Option("DEADBEEF".getBytes)): Option[Hobby] = {
101-
val await = Try(Await.result(getHobbiesAsync(s, i, l, bool, f, d, datetime, date, b, bin), Duration.Inf))
101+
def getHobbies(s: Option[String] = Option("some string"), `class`: Option[String] = Option("some string"), i: Option[Integer] = Option(1), l: Option[Long] = Option(2), bool: Option[Boolean] = Option(true), f: Option[Float] = Option(0.1), d: Option[Double] = Option(10.005), datetime: Option[Date] = Option(dateTimeFormatter.parse("2018-01-01T08:30:00Z-04:00")), date: Option[Date] = Option(dateFormatter.parse("2018-01-01")), b: Option[Array[Byte]] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes), bin: Option[Array[Byte]] = Option("DEADBEEF".getBytes)): Option[Hobby] = {
102+
val await = Try(Await.result(getHobbiesAsync(s, `class`, i, l, bool, f, d, datetime, date, b, bin), Duration.Inf))
102103
await match {
103104
case Success(i) => Some(await.get)
104105
case Failure(t) => None
@@ -110,6 +111,7 @@ class HobbiesApi(
110111
* Query hobbies with some additional optional meaningless parameters
111112
*
112113
* @param s a string (optional, default to some string)
114+
* @param `class` a string, testing keyword escaping (optional, default to some string)
113115
* @param i an integer (optional, default to 1)
114116
* @param l a long (optional, default to 2)
115117
* @param bool a bool (optional, default to true)
@@ -121,24 +123,25 @@ class HobbiesApi(
121123
* @param bin an octet string (optional, default to DEADBEEF)
122124
* @return Future(Hobby)
123125
*/
124-
def getHobbiesAsync(s: Option[String] = Option("some string"), i: Option[Integer] = Option(1), l: Option[Long] = Option(2), bool: Option[Boolean] = Option(true), f: Option[Float] = Option(0.1), d: Option[Double] = Option(10.005), datetime: Option[Date] = Option(dateTimeFormatter.parse("2018-01-01T08:30:00Z-04:00")), date: Option[Date] = Option(dateFormatter.parse("2018-01-01")), b: Option[ArrayByte] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes), bin: Option[ArrayByte] = Option("DEADBEEF".getBytes)): Future[Hobby] = {
125-
helper.getHobbies(s, i, l, bool, f, d, datetime, date, b, bin)
126+
def getHobbiesAsync(s: Option[String] = Option("some string"), `class`: Option[String] = Option("some string"), i: Option[Integer] = Option(1), l: Option[Long] = Option(2), bool: Option[Boolean] = Option(true), f: Option[Float] = Option(0.1), d: Option[Double] = Option(10.005), datetime: Option[Date] = Option(dateTimeFormatter.parse("2018-01-01T08:30:00Z-04:00")), date: Option[Date] = Option(dateFormatter.parse("2018-01-01")), b: Option[Array[Byte]] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes), bin: Option[Array[Byte]] = Option("DEADBEEF".getBytes)): Future[Hobby] = {
127+
helper.getHobbies(s, `class`, i, l, bool, f, d, datetime, date, b, bin)
126128
}
127129

128130
}
129131

130132
class HobbiesApiAsyncHelper(client: TransportClient, config: SwaggerConfig) extends ApiClient(client, config) {
131133

132134
def getHobbies(s: Option[String] = Option("some string"),
135+
`class`: Option[String] = Option("some string"),
133136
i: Option[Integer] = Option(1),
134137
l: Option[Long] = Option(2),
135138
bool: Option[Boolean] = Option(true),
136139
f: Option[Float] = Option(0.1),
137140
d: Option[Double] = Option(10.005),
138141
datetime: Option[Date] = Option(dateTimeFormatter.parse("2018-01-01T08:30:00Z-04:00")),
139142
date: Option[Date] = Option(dateFormatter.parse("2018-01-01")),
140-
b: Option[ArrayByte] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes),
141-
bin: Option[ArrayByte] = Option("DEADBEEF".getBytes)
143+
b: Option[Array[Byte]] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes),
144+
bin: Option[Array[Byte]] = Option("DEADBEEF".getBytes)
142145
)(implicit reader: ClientResponseReader[Hobby]): Future[Hobby] = {
143146
// create path and map variables
144147
val path = (addFmt("/hobbies"))
@@ -151,6 +154,10 @@ class HobbiesApiAsyncHelper(client: TransportClient, config: SwaggerConfig) exte
151154
case Some(param) => queryParams += "s" -> param.toString
152155
case _ => queryParams
153156
}
157+
`class` match {
158+
case Some(param) => queryParams += "class" -> param.toString
159+
case _ => queryParams
160+
}
154161
i match {
155162
case Some(param) => queryParams += "i" -> param.toString
156163
case _ => queryParams

modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Hobby.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ case class Hobby (
2323
enabled: Option[Boolean] = None,
2424
created: Option[Date] = None,
2525
timestamp: Option[Date] = None,
26-
bytes: Option[ArrayByte] = None,
26+
bytes: Option[Array[Byte]] = None,
2727
binary: Option[String] = None
2828
)
2929

modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-spec.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@
167167
"in": "query",
168168
"default": "some string"
169169
},
170+
{
171+
"type": "string",
172+
"description": "a string, testing keyword escaping",
173+
"name": "class",
174+
"required": false,
175+
"in": "query",
176+
"default": "some string"
177+
},
170178
{
171179
"type": "integer",
172180
"format": "int32",

samples/client/petstore/scala/src/main/scala/io/swagger/client/model/ApiResponse.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ package io.swagger.client.model
1515

1616
case class ApiResponse (
1717
code: Option[Integer] = None,
18-
_type: Option[String] = None,
18+
`type`: Option[String] = None,
1919
message: Option[String] = None
2020
)
2121

0 commit comments

Comments
 (0)