diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractScalaCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractScalaCodegen.java index 3ef8b2e06bb..85afe5d16ca 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractScalaCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractScalaCodegen.java @@ -6,6 +6,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import com.samskivert.mustache.Escapers; +import com.samskivert.mustache.Mustache; import io.swagger.codegen.CliOption; import io.swagger.codegen.CodegenConstants; import io.swagger.codegen.DefaultCodegen; @@ -43,7 +45,50 @@ public abstract class AbstractScalaCodegen extends DefaultCodegen { "Any", "List", "Seq", - "Map")); + "Map", + "Array")); + + reservedWords.addAll(Arrays.asList( + "abstract", + "case", + "catch", + "class", + "def", + "do", + "else", + "extends", + "false", + "final", + "finally", + "for", + "forSome", + "if", + "implicit", + "import", + "lazy", + "match", + "new", + "null", + "object", + "override", + "package", + "private", + "protected", + "return", + "sealed", + "super", + "this", + "throw", + "trait", + "try", + "true", + "type", + "val", + "var", + "while", + "with", + "yield" + )); cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC)); cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)); @@ -72,7 +117,30 @@ public abstract class AbstractScalaCodegen extends DefaultCodegen { if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); } - return "_" + name; + // Reserved words will be further escaped at the mustache compiler level. + // Scala escaping done here (via `, without compiler escaping) would otherwise be HTML encoded. + return "`" + name + "`"; + } + + @Override + public Mustache.Compiler processCompiler(Mustache.Compiler compiler) { + Mustache.Escaper SCALA = new Mustache.Escaper() { + @Override public String escape (String text) { + // Fix included as suggested by akkie in #6393 + // The given text is a reserved word which is escaped by enclosing it with grave accents. If we would + // escape that with the default Mustache `HTML` escaper, then the escaper would also escape our grave + // accents. So we remove the grave accents before the escaping and add it back after the escaping. + if (text.startsWith("`") && text.endsWith("`")) { + String unescaped = text.substring(1, text.length() - 1); + return "`" + Escapers.HTML.escape(unescaped) + "`"; + } + + // All none reserved words will be escaped with the default Mustache `HTML` escaper + return Escapers.HTML.escape(text); + } + }; + + return compiler.withEscaper(SCALA); } @Override diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ScalaClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ScalaClientCodegen.java index 8316e5563ba..d84e7bddae3 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ScalaClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ScalaClientCodegen.java @@ -5,7 +5,6 @@ import io.swagger.codegen.*; import java.io.File; import java.util.Arrays; import java.util.HashMap; -import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -96,6 +95,7 @@ public class ScalaClientCodegen extends AbstractScalaCodegen implements CodegenC typeMapping.put("file", "File"); typeMapping.put("binary", "Array[Byte]"); typeMapping.put("ByteArray", "Array[Byte]"); + typeMapping.put("ArrayByte", "Array[Byte]"); typeMapping.put("date-time", "Date"); typeMapping.put("DateTime", "Date"); diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/languages/AbstractScalaCodegenTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/languages/AbstractScalaCodegenTest.java index 415f346c1d4..e29b9fd09c2 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/languages/AbstractScalaCodegenTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/languages/AbstractScalaCodegenTest.java @@ -37,7 +37,8 @@ public class AbstractScalaCodegenTest { String result = abstractScalaCodegen.formatIdentifier(className, true); - Assert.assertTrue("_ReservedWord".equals(result)); + // NOTE: reserved words are further escaped at the compiler level. + Assert.assertTrue("`ReservedWord`".equals(result)); } @Test diff --git a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/HobbiesApi.scala b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/HobbiesApi.scala index 325b0069bbb..3f8ca7c4880 100644 --- a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/HobbiesApi.scala +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/HobbiesApi.scala @@ -86,6 +86,7 @@ class HobbiesApi( * Query hobbies with some additional optional meaningless parameters * * @param s a string (optional, default to some string) + * @param `class` a string, testing keyword escaping (optional, default to some string) * @param i an integer (optional, default to 1) * @param l a long (optional, default to 2) * @param bool a bool (optional, default to true) @@ -97,8 +98,8 @@ class HobbiesApi( * @param bin an octet string (optional, default to DEADBEEF) * @return Hobby */ - 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] = { - val await = Try(Await.result(getHobbiesAsync(s, i, l, bool, f, d, datetime, date, b, bin), Duration.Inf)) + 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] = { + val await = Try(Await.result(getHobbiesAsync(s, `class`, i, l, bool, f, d, datetime, date, b, bin), Duration.Inf)) await match { case Success(i) => Some(await.get) case Failure(t) => None @@ -110,6 +111,7 @@ class HobbiesApi( * Query hobbies with some additional optional meaningless parameters * * @param s a string (optional, default to some string) + * @param `class` a string, testing keyword escaping (optional, default to some string) * @param i an integer (optional, default to 1) * @param l a long (optional, default to 2) * @param bool a bool (optional, default to true) @@ -121,8 +123,8 @@ class HobbiesApi( * @param bin an octet string (optional, default to DEADBEEF) * @return Future(Hobby) */ - 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] = { - helper.getHobbies(s, i, l, bool, f, d, datetime, date, b, bin) + 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] = { + helper.getHobbies(s, `class`, i, l, bool, f, d, datetime, date, b, bin) } } @@ -130,6 +132,7 @@ class HobbiesApi( class HobbiesApiAsyncHelper(client: TransportClient, config: SwaggerConfig) extends ApiClient(client, config) { 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), @@ -137,8 +140,8 @@ class HobbiesApiAsyncHelper(client: TransportClient, config: SwaggerConfig) exte 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) + b: Option[Array[Byte]] = Option("c3dhZ2dlciBjb2RlZ2Vu".getBytes), + bin: Option[Array[Byte]] = Option("DEADBEEF".getBytes) )(implicit reader: ClientResponseReader[Hobby]): Future[Hobby] = { // create path and map variables val path = (addFmt("/hobbies")) @@ -151,6 +154,10 @@ class HobbiesApiAsyncHelper(client: TransportClient, config: SwaggerConfig) exte case Some(param) => queryParams += "s" -> param.toString case _ => queryParams } + `class` match { + case Some(param) => queryParams += "class" -> param.toString + case _ => queryParams + } i match { case Some(param) => queryParams += "i" -> param.toString case _ => queryParams diff --git a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Hobby.scala b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Hobby.scala index 30782d0119f..02f64681fa4 100644 --- a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Hobby.scala +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Hobby.scala @@ -23,7 +23,7 @@ case class Hobby ( enabled: Option[Boolean] = None, created: Option[Date] = None, timestamp: Option[Date] = None, - bytes: Option[ArrayByte] = None, + bytes: Option[Array[Byte]] = None, binary: Option[String] = None ) diff --git a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-spec.json b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-spec.json index 00829eb2655..db23005df4a 100644 --- a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-spec.json +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-spec.json @@ -167,6 +167,14 @@ "in": "query", "default": "some string" }, + { + "type": "string", + "description": "a string, testing keyword escaping", + "name": "class", + "required": false, + "in": "query", + "default": "some string" + }, { "type": "integer", "format": "int32", diff --git a/samples/client/petstore/scala/src/main/scala/io/swagger/client/model/ApiResponse.scala b/samples/client/petstore/scala/src/main/scala/io/swagger/client/model/ApiResponse.scala index 220a5e48d9e..2717e1bcb72 100644 --- a/samples/client/petstore/scala/src/main/scala/io/swagger/client/model/ApiResponse.scala +++ b/samples/client/petstore/scala/src/main/scala/io/swagger/client/model/ApiResponse.scala @@ -15,7 +15,7 @@ package io.swagger.client.model case class ApiResponse ( code: Option[Integer] = None, - _type: Option[String] = None, + `type`: Option[String] = None, message: Option[String] = None )