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 14f8f4e182a..8316e5563ba 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 @@ -94,13 +94,9 @@ public ScalaClientCodegen() { typeMapping.put("double", "Double"); typeMapping.put("object", "Any"); typeMapping.put("file", "File"); - //TODO binary should be mapped to byte array - // mapped to String as a workaround - typeMapping.put("binary", "String"); - typeMapping.put("ByteArray", "String"); + typeMapping.put("binary", "Array[Byte]"); + typeMapping.put("ByteArray", "Array[Byte]"); typeMapping.put("date-time", "Date"); -// typeMapping.put("date", "Date"); -// typeMapping.put("Date", "Date"); typeMapping.put("DateTime", "Date"); instantiationTypes.put("array", "ListBuffer"); diff --git a/modules/swagger-codegen/src/main/resources/scala/api.mustache b/modules/swagger-codegen/src/main/resources/scala/api.mustache index 68caa44bf68..f67133052c8 100644 --- a/modules/swagger-codegen/src/main/resources/scala/api.mustache +++ b/modules/swagger-codegen/src/main/resources/scala/api.mustache @@ -14,6 +14,7 @@ import javax.ws.rs.core.MediaType import java.io.File import java.util.Date +import java.util.TimeZone import scala.collection.mutable.HashMap @@ -38,9 +39,18 @@ class {{classname}}( val defBasePath: String = "{{{basePath}}}", defApiInvoker: ApiInvoker = ApiInvoker ) { - + private lazy val dateTimeFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + private val dateFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } implicit val formats = new org.json4s.DefaultFormats { - override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+0000") + override def dateFormatter = dateTimeFormatter } implicit val stringReader: ClientResponseReader[String] = ClientResponseReaders.StringReader implicit val unitReader: ClientResponseReader[Unit] = ClientResponseReaders.UnitReader @@ -68,7 +78,7 @@ class {{classname}}( {{#allParams}} * @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} {{/allParams}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} */ - def {{operationId}}({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{#defaultValue}} /* = {{{defaultValue}}}*/{{/defaultValue}}{{/required}}{{^required}}Option[{{dataType}}]{{#defaultValue}} = None /* = {{{defaultValue}}}*/{{/defaultValue}}{{^defaultValue}} = None{{/defaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}: Option[{{returnType}}]{{/returnType}} = { + def {{operationId}}({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{#defaultValue}} = {{#isString}}"{{{defaultValue}}}"{{/isString}}{{^isString}}{{#isByteArray}}"{{/isByteArray}}{{#isDate}}dateFormatter.parse("{{/isDate}}{{#isDateTime}}dateTimeFormatter.parse("{{/isDateTime}}{{{defaultValue}}}{{#isDate}}"){{/isDate}}{{#isDateTime}}"){{/isDateTime}}{{#isByteArray}}".getBytes{{/isByteArray}}{{/isString}}{{/defaultValue}}{{/required}}{{^required}}Option[{{dataType}}]{{#defaultValue}} = Option({{#isString}}"{{{defaultValue}}}"{{/isString}}{{^isString}}{{#isByteArray}}"{{/isByteArray}}{{#isDate}}dateFormatter.parse("{{/isDate}}{{#isDateTime}}dateTimeFormatter.parse("{{/isDateTime}}{{{defaultValue}}}{{#isDate}}"){{/isDate}}{{#isDateTime}}"){{/isDateTime}}{{#isByteArray}}".getBytes{{/isByteArray}}{{/isString}}){{/defaultValue}}{{^defaultValue}} = None{{/defaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}: Option[{{returnType}}]{{/returnType}} = { val await = Try(Await.result({{operationId}}Async({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}), Duration.Inf)) await match { case Success(i) => Some(await.get) @@ -83,7 +93,7 @@ class {{classname}}( {{#allParams}} * @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} {{/allParams}} * @return Future({{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}) */ - def {{operationId}}Async({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{#defaultValue}} /* = {{{defaultValue}}}*/{{/defaultValue}}{{/required}}{{^required}}Option[{{dataType}}]{{#defaultValue}} = None /* = {{{defaultValue}}}*/{{/defaultValue}}{{^defaultValue}} = None{{/defaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}: Future[{{returnType}}]{{/returnType}} = { + def {{operationId}}Async({{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{#defaultValue}} = {{#isString}}"{{{defaultValue}}}"{{/isString}}{{^isString}}{{#isByteArray}}"{{/isByteArray}}{{#isDate}}dateFormatter.parse("{{/isDate}}{{#isDateTime}}dateTimeFormatter.parse("{{/isDateTime}}{{{defaultValue}}}{{#isDate}}"){{/isDate}}{{#isDateTime}}"){{/isDateTime}}{{#isByteArray}}".getBytes{{/isByteArray}}{{/isString}}{{/defaultValue}}{{/required}}{{^required}}Option[{{dataType}}]{{#defaultValue}} = Option({{#isString}}"{{{defaultValue}}}"{{/isString}}{{^isString}}{{#isByteArray}}"{{/isByteArray}}{{#isDate}}dateFormatter.parse("{{/isDate}}{{#isDateTime}}dateTimeFormatter.parse("{{/isDateTime}}{{{defaultValue}}}{{#isDate}}"){{/isDate}}{{#isDateTime}}"){{/isDateTime}}{{#isByteArray}}".getBytes{{/isByteArray}}{{/isString}}){{/defaultValue}}{{^defaultValue}} = None{{/defaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}}: Future[{{returnType}}]{{/returnType}} = { helper.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) } @@ -93,8 +103,8 @@ class {{classname}}( class {{classname}}AsyncHelper(client: TransportClient, config: SwaggerConfig) extends ApiClient(client, config) { {{#operation}} - def {{operationId}}({{#allParams}}{{^required}}{{paramName}}: Option[{{dataType}}] = {{#defaultValue}}Some({{defaultValue}}){{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{#hasMore}},{{/hasMore}} - {{/required}}{{#required}}{{paramName}}: {{dataType}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{#hasMore}}, + def {{operationId}}({{#allParams}}{{^required}}{{paramName}}: Option[{{dataType}}] = {{#defaultValue}}Option({{#isString}}"{{{defaultValue}}}"{{/isString}}{{^isString}}{{#isByteArray}}"{{/isByteArray}}{{#isDate}}dateFormatter.parse("{{/isDate}}{{#isDateTime}}dateTimeFormatter.parse("{{/isDateTime}}{{{defaultValue}}}{{#isDate}}"){{/isDate}}{{#isDateTime}}"){{/isDateTime}}{{#isByteArray}}".getBytes{{/isByteArray}}{{/isString}}){{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{#hasMore}},{{/hasMore}} + {{/required}}{{#required}}{{paramName}}: {{dataType}}{{#defaultValue}} = {{#isString}}"{{{defaultValue}}}"{{/isString}}{{^isString}}{{#isByteArray}}"{{/isByteArray}}{{#isDate}}dateFormatter.parse("{{/isDate}}{{#isDateTime}}dateTimeFormatter.parse("{{/isDateTime}}{{{defaultValue}}}{{#isDate}}"){{/isDate}}{{#isDateTime}}"){{/isDateTime}}{{#isByteArray}}".getBytes{{/isByteArray}}{{/isString}}{{/defaultValue}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}})(implicit reader: ClientResponseReader[{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}}]{{#bodyParams}}, writer: RequestWriter[{{^required}}Option[{{dataType}}]{{/required}}{{#required}}{{dataType}}{{/required}}]{{/bodyParams}}){{#returnType}}: Future[{{returnType}}]{{/returnType}}{{^returnType}}: Future[Unit]{{/returnType}} = { // create path and map variables val path = (addFmt("{{path}}"){{#pathParams}} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/scala/ScalaClientRequiredAttributesIntegrationTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/scala/ScalaClientRequiredAttributesIntegrationTest.java new file mode 100644 index 00000000000..9c7520f0a95 --- /dev/null +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/scala/ScalaClientRequiredAttributesIntegrationTest.java @@ -0,0 +1,54 @@ +package io.swagger.codegen.scala; + +import com.google.common.collect.ImmutableMap; +import io.swagger.codegen.AbstractIntegrationTest; +import io.swagger.codegen.CodegenConfig; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.languages.ScalaClientCodegen; +import io.swagger.codegen.testutils.IntegrationTestPathsConfig; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class ScalaClientRequiredAttributesIntegrationTest extends AbstractIntegrationTest { + + public ScalaClientRequiredAttributesIntegrationTest() { + generateSwaggerMetadata = false; + + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + systemPropertyOverrides = builder + .put(CodegenConstants.APIS, Boolean.TRUE.toString()) + .put(CodegenConstants.MODELS, Boolean.TRUE.toString()) + .put(CodegenConstants.API_DOCS, Boolean.FALSE.toString()) + .put(CodegenConstants.MODEL_DOCS, Boolean.FALSE.toString()) + .put(CodegenConstants.API_TESTS, Boolean.FALSE.toString()) + .put(CodegenConstants.MODEL_TESTS, Boolean.FALSE.toString()) + .put(CodegenConstants.SUPPORTING_FILES, Boolean.FALSE.toString()) + .build(); + } + + @Override + protected IntegrationTestPathsConfig getIntegrationTestPathsConfig() { + return new IntegrationTestPathsConfig("scala/client/required-attributes"); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return new ScalaClientCodegen(); + } + + @Override + protected Map configProperties() { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.EXCLUDE_TESTS, Boolean.TRUE.toString()); + return properties; + } + + // TODO: Remove this when super.generatesCorrectDirectoryStructure() is re-enabled. + @Test(description = "Verify Scala client's understanding of 'required' attributes. (disabled awaiting CI fix for integration tests classpath)", enabled = false) + public void test() throws IOException { + this.generatesCorrectDirectoryStructure(); + } +} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/testutils/AssertFile.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/testutils/AssertFile.java index aca2beb2dc5..5b136092d55 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/testutils/AssertFile.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/testutils/AssertFile.java @@ -9,6 +9,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; import java.util.List; import difflib.Delta; @@ -56,8 +57,18 @@ public FileVisitResult preVisitDirectory(Path expectedDir, BasicFileAttributes a fail(String.format("Directory '%s' is missing.", actualDir)); } - assertEquals(expectedDir.toFile().list(), - actualDir.toFile().list(), + String[] expected = expectedDir.toFile().list(); + String[] actual = actualDir.toFile().list(); + + if (expected != null) { + Arrays.sort(expected); + } + if (actual != null) { + Arrays.sort(actual); + } + + assertEquals(expected, + actual, String.format("Directory content of '%s' and '%s' differ.", expectedDir, actualDir)); return FileVisitResult.CONTINUE; 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 new file mode 100644 index 00000000000..325b0069bbb --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/HobbiesApi.scala @@ -0,0 +1,198 @@ +/** + * Scala Client API Integration Test + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: 1 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +package io.swagger.client.api + +import java.text.SimpleDateFormat + +import io.swagger.client.model.ArrayByte +import java.util.Date +import io.swagger.client.model.Hobby +import io.swagger.client.{ApiInvoker, ApiException} + +import com.sun.jersey.multipart.FormDataMultiPart +import com.sun.jersey.multipart.file.FileDataBodyPart + +import javax.ws.rs.core.MediaType + +import java.io.File +import java.util.Date +import java.util.TimeZone + +import scala.collection.mutable.HashMap + +import com.wordnik.swagger.client._ +import scala.concurrent.Future +import collection.mutable + +import java.net.URI + +import com.wordnik.swagger.client.ClientResponseReaders.Json4sFormatsReader._ +import com.wordnik.swagger.client.RequestWriters.Json4sFormatsWriter._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.util.{Failure, Success, Try} + +import org.json4s._ + +class HobbiesApi( + val defBasePath: String = "https://localhost:8080", + defApiInvoker: ApiInvoker = ApiInvoker +) { + private lazy val dateTimeFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + private val dateFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + implicit val formats = new org.json4s.DefaultFormats { + override def dateFormatter = dateTimeFormatter + } + implicit val stringReader: ClientResponseReader[String] = ClientResponseReaders.StringReader + implicit val unitReader: ClientResponseReader[Unit] = ClientResponseReaders.UnitReader + implicit val jvalueReader: ClientResponseReader[JValue] = ClientResponseReaders.JValueReader + implicit val jsonReader: ClientResponseReader[Nothing] = JsonFormatsReader + implicit val stringWriter: RequestWriter[String] = RequestWriters.StringWriter + implicit val jsonWriter: RequestWriter[Nothing] = JsonFormatsWriter + + var basePath: String = defBasePath + var apiInvoker: ApiInvoker = defApiInvoker + + def addHeader(key: String, value: String): mutable.HashMap[String, String] = { + apiInvoker.defaultHeaders += key -> value + } + + val config: SwaggerConfig = SwaggerConfig.forUrl(new URI(defBasePath)) + val client = new RestClient(config) + val helper = new HobbiesApiAsyncHelper(client, config) + + /** + * Get hobbies + * Query hobbies with some additional optional meaningless parameters + * + * @param s a string (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) + * @param f a float (optional, default to 0.1) + * @param d a double (optional, default to 10.005) + * @param datetime a date time (optional, default to 2018-01-01T08:30:00Z-04:00) + * @param date a date (optional, default to 2018-01-01) + * @param b a base64 encoded string (optional, default to c3dhZ2dlciBjb2RlZ2Vu) + * @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)) + await match { + case Success(i) => Some(await.get) + case Failure(t) => None + } + } + + /** + * Get hobbies asynchronously + * Query hobbies with some additional optional meaningless parameters + * + * @param s a string (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) + * @param f a float (optional, default to 0.1) + * @param d a double (optional, default to 10.005) + * @param datetime a date time (optional, default to 2018-01-01T08:30:00Z-04:00) + * @param date a date (optional, default to 2018-01-01) + * @param b a base64 encoded string (optional, default to c3dhZ2dlciBjb2RlZ2Vu) + * @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) + } + +} + +class HobbiesApiAsyncHelper(client: TransportClient, config: SwaggerConfig) extends ApiClient(client, config) { + + 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) + )(implicit reader: ClientResponseReader[Hobby]): Future[Hobby] = { + // create path and map variables + val path = (addFmt("/hobbies")) + + // query params + val queryParams = new mutable.HashMap[String, String] + val headerParams = new mutable.HashMap[String, String] + + s match { + case Some(param) => queryParams += "s" -> param.toString + case _ => queryParams + } + i match { + case Some(param) => queryParams += "i" -> param.toString + case _ => queryParams + } + l match { + case Some(param) => queryParams += "l" -> param.toString + case _ => queryParams + } + bool match { + case Some(param) => queryParams += "bool" -> param.toString + case _ => queryParams + } + f match { + case Some(param) => queryParams += "f" -> param.toString + case _ => queryParams + } + d match { + case Some(param) => queryParams += "d" -> param.toString + case _ => queryParams + } + datetime match { + case Some(param) => queryParams += "datetime" -> param.toString + case _ => queryParams + } + date match { + case Some(param) => queryParams += "date" -> param.toString + case _ => queryParams + } + b match { + case Some(param) => queryParams += "b" -> param.toString + case _ => queryParams + } + bin match { + case Some(param) => queryParams += "bin" -> param.toString + case _ => queryParams + } + + val resFuture = client.submit("GET", path, queryParams.toMap, headerParams.toMap, "") + resFuture flatMap { resp => + process(reader.read(resp)) + } + } + + +} diff --git a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/PeopleApi.scala b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/PeopleApi.scala new file mode 100644 index 00000000000..ca5a245b255 --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/api/PeopleApi.scala @@ -0,0 +1,225 @@ +/** + * Scala Client API Integration Test + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: 1 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +package io.swagger.client.api + +import java.text.SimpleDateFormat + +import io.swagger.client.model.Person +import io.swagger.client.{ApiInvoker, ApiException} + +import com.sun.jersey.multipart.FormDataMultiPart +import com.sun.jersey.multipart.file.FileDataBodyPart + +import javax.ws.rs.core.MediaType + +import java.io.File +import java.util.Date +import java.util.TimeZone + +import scala.collection.mutable.HashMap + +import com.wordnik.swagger.client._ +import scala.concurrent.Future +import collection.mutable + +import java.net.URI + +import com.wordnik.swagger.client.ClientResponseReaders.Json4sFormatsReader._ +import com.wordnik.swagger.client.RequestWriters.Json4sFormatsWriter._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.util.{Failure, Success, Try} + +import org.json4s._ + +class PeopleApi( + val defBasePath: String = "https://localhost:8080", + defApiInvoker: ApiInvoker = ApiInvoker +) { + private lazy val dateTimeFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + private val dateFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + implicit val formats = new org.json4s.DefaultFormats { + override def dateFormatter = dateTimeFormatter + } + implicit val stringReader: ClientResponseReader[String] = ClientResponseReaders.StringReader + implicit val unitReader: ClientResponseReader[Unit] = ClientResponseReaders.UnitReader + implicit val jvalueReader: ClientResponseReader[JValue] = ClientResponseReaders.JValueReader + implicit val jsonReader: ClientResponseReader[Nothing] = JsonFormatsReader + implicit val stringWriter: RequestWriter[String] = RequestWriters.StringWriter + implicit val jsonWriter: RequestWriter[Nothing] = JsonFormatsWriter + + var basePath: String = defBasePath + var apiInvoker: ApiInvoker = defApiInvoker + + def addHeader(key: String, value: String): mutable.HashMap[String, String] = { + apiInvoker.defaultHeaders += key -> value + } + + val config: SwaggerConfig = SwaggerConfig.forUrl(new URI(defBasePath)) + val client = new RestClient(config) + val helper = new PeopleApiAsyncHelper(client, config) + + /** + * People listing + * + * + * @param age Limit results to people of a certain age. (optional) + * @return List[Person] + */ + def getPeople(age: Option[Long] = None): Option[List[Person]] = { + val await = Try(Await.result(getPeopleAsync(age), Duration.Inf)) + await match { + case Success(i) => Some(await.get) + case Failure(t) => None + } + } + + /** + * People listing asynchronously + * + * + * @param age Limit results to people of a certain age. (optional) + * @return Future(List[Person]) + */ + def getPeopleAsync(age: Option[Long] = None): Future[List[Person]] = { + helper.getPeople(age) + } + + /** + * get people by id + * Retrieves a single person record by personId + * + * @param personId The person's ID. + * @return Person + */ + def getPersonById(personId: Long): Option[Person] = { + val await = Try(Await.result(getPersonByIdAsync(personId), Duration.Inf)) + await match { + case Success(i) => Some(await.get) + case Failure(t) => None + } + } + + /** + * get people by id asynchronously + * Retrieves a single person record by personId + * + * @param personId The person's ID. + * @return Future(Person) + */ + def getPersonByIdAsync(personId: Long): Future[Person] = { + helper.getPersonById(personId) + } + + /** + * People batch save + * + * + * @param body Batch input of people to save + * @param size Explicitly sets the batch size (optional, default to 25) + * @return List[Person] + */ + def savePeople(body: List[Person], size: Option[Integer] = Option(25)): Option[List[Person]] = { + val await = Try(Await.result(savePeopleAsync(body, size), Duration.Inf)) + await match { + case Success(i) => Some(await.get) + case Failure(t) => None + } + } + + /** + * People batch save asynchronously + * + * + * @param body Batch input of people to save + * @param size Explicitly sets the batch size (optional, default to 25) + * @return Future(List[Person]) + */ + def savePeopleAsync(body: List[Person], size: Option[Integer] = Option(25)): Future[List[Person]] = { + helper.savePeople(body, size) + } + +} + +class PeopleApiAsyncHelper(client: TransportClient, config: SwaggerConfig) extends ApiClient(client, config) { + + def getPeople(age: Option[Long] = None + )(implicit reader: ClientResponseReader[List[Person]]): Future[List[Person]] = { + // create path and map variables + val path = (addFmt("/people")) + + // query params + val queryParams = new mutable.HashMap[String, String] + val headerParams = new mutable.HashMap[String, String] + + age match { + case Some(param) => queryParams += "age" -> param.toString + case _ => queryParams + } + + val resFuture = client.submit("GET", path, queryParams.toMap, headerParams.toMap, "") + resFuture flatMap { resp => + process(reader.read(resp)) + } + } + + def getPersonById(personId: Long)(implicit reader: ClientResponseReader[Person]): Future[Person] = { + // create path and map variables + val path = (addFmt("/people/{personId}") + replaceAll("\\{" + "personId" + "\\}", personId.toString)) + + // query params + val queryParams = new mutable.HashMap[String, String] + val headerParams = new mutable.HashMap[String, String] + + + val resFuture = client.submit("GET", path, queryParams.toMap, headerParams.toMap, "") + resFuture flatMap { resp => + process(reader.read(resp)) + } + } + + def savePeople(body: List[Person], + size: Option[Integer] = Option(25) + )(implicit reader: ClientResponseReader[List[Person]], writer: RequestWriter[List[Person]]): Future[List[Person]] = { + // create path and map variables + val path = (addFmt("/people")) + + // query params + val queryParams = new mutable.HashMap[String, String] + val headerParams = new mutable.HashMap[String, String] + + if (body == null) throw new Exception("Missing required parameter 'body' when calling PeopleApi->savePeople") + size match { + case Some(param) => queryParams += "size" -> param.toString + case _ => queryParams + } + + val resFuture = client.submit("POST", path, queryParams.toMap, headerParams.toMap, writer.write(body)) + resFuture flatMap { resp => + process(reader.read(resp)) + } + } + + +} 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 new file mode 100644 index 00000000000..30782d0119f --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Hobby.scala @@ -0,0 +1,29 @@ +/** + * Scala Client API Integration Test + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: 1 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +package io.swagger.client.model + +import java.util.Date + +case class Hobby ( + id: Option[Long] = None, + name: Option[String] = None, + count: Option[Integer] = None, + rate: Option[Float] = None, + ratio: Option[Double] = None, + enabled: Option[Boolean] = None, + created: Option[Date] = None, + timestamp: Option[Date] = None, + bytes: Option[ArrayByte] = None, + binary: Option[String] = None +) + diff --git a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Person.scala b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Person.scala new file mode 100644 index 00000000000..cd78718762b --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-expected/src/main/scala/io/swagger/client/model/Person.scala @@ -0,0 +1,22 @@ +/** + * Scala Client API Integration Test + * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) + * + * OpenAPI spec version: 1 + * + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +package io.swagger.client.model + + +case class Person ( + id: Long, + firstName: String, + lastName: String, + age: Option[Integer] = 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 new file mode 100644 index 00000000000..00829eb2655 --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes-spec.json @@ -0,0 +1,332 @@ +{ + "swagger": "2.0", + "info": { + "version": "1", + "title": "Scala Client API Integration Test" + }, + "host": "localhost:8080", + "basePath": "/", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/people": { + "get": { + "tags": [ + "People" + ], + "operationId": "getPeople", + "summary": "People listing", + "produces": [ + "application/json" + ], + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "Limit results to people of a certain age.", + "name": "age", + "required": false, + "in": "query" + } + ], + "responses": { + "200": { + "description": "200 OK Response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Person" + } + } + } + } + }, + "post": { + "tags": [ + "People" + ], + "operationId": "savePeople", + "summary": "People batch save", + "produces": [ + "application/json" + ], + "parameters": [ + { + "description": "Batch input of people to save", + "name": "body", + "required": true, + "in": "body", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Person" + } + } + }, + { + "type": "integer", + "format": "int32", + "description": "Explicitly sets the batch size", + "name": "size", + "required": false, + "in": "query", + "minimum": "10", + "maximum": "250", + "default": "25" + } + ], + "responses": { + "200": { + "description": "200 OK Response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Person" + } + } + } + } + } + }, + "/people/{personId}": { + "get": { + "tags": [ + "People" + ], + "summary": "get people by id", + "description": "Retrieves a single person record by personId", + "operationId": "getPersonById", + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "The person requested", + "schema": { + "$ref": "#/definitions/Person" + } + }, + "404": { + "description": "No person with the provided id was found" + }, + "400": { + "description": "Invalid person id" + } + }, + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "The person's ID.", + "name": "personId", + "required": true, + "in": "path" + } + ] + } + }, + "/hobbies": { + "get": { + "tags": [ + "Hobbies" + ], + "summary": "Get hobbies", + "description": "Query hobbies with some additional optional meaningless parameters", + "operationId": "getHobbies", + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "The list of hobbies", + "schema": { + "type": "array", + "$ref": "#/definitions/Hobby" + } + }, + "404": { + "description": "No hobbies." + }, + "400": { + "description": "There was an issue with your request." + } + }, + "parameters": [ + { + "type": "string", + "description": "a string", + "name": "s", + "required": false, + "in": "query", + "default": "some string" + }, + { + "type": "integer", + "format": "int32", + "description": "an integer", + "name": "i", + "required": false, + "in": "query", + "default": 1 + }, + { + "type": "integer", + "format": "int64", + "description": "a long", + "name": "l", + "required": false, + "in": "query", + "default": 2 + }, + { + "type": "boolean", + "description": "a bool", + "name": "bool", + "required": false, + "in": "query", + "default": true + }, + { + "type": "number", + "format": "float", + "description": "a float", + "name": "f", + "required": false, + "in": "query", + "default": 0.1 + }, + { + "type": "number", + "format": "double", + "description": "a double", + "name": "d", + "required": false, + "in": "query", + "default": 10.005 + }, + { + "type": "string", + "format": "date-time", + "description": "a date time", + "name": "datetime", + "required": false, + "in": "query", + "default": "2018-01-01T08:30:00Z-04:00" + }, + { + "type": "string", + "format": "date", + "description": "a date", + "name": "date", + "required": false, + "in": "query", + "default": "2018-01-01" + }, + { + "type": "string", + "format": "byte", + "description": "a base64 encoded string", + "name": "b", + "required": false, + "in": "query", + "default": "c3dhZ2dlciBjb2RlZ2Vu" + }, + { + "type": "string", + "format": "binary", + "description": "an octet string", + "name": "bin", + "required": false, + "in": "query", + "default": "DEADBEEF" + } + ] + } + } + }, + "definitions": { + "Person": { + "type": "object", + "required": ["id","firstName","lastName"], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string", + "example": "text" + }, + "lastName": { + "type": "string", + "example": "text" + }, + "age": { + "type": "integer", + "format": "int32" + } + } + }, + "Hobby": { + "type": "object", + "required": [], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "default": -1 + }, + "name": { + "type": "string", + "default": "Hobby Name" + }, + "count": { + "type": "integer", + "format": "int32", + "default": 1 + }, + "rate": { + "type": "number", + "format": "float", + "default": 10.0 + }, + "ratio": { + "type": "number", + "format": "double", + "default": 0.0005 + }, + "enabled": { + "type": "boolean", + "default": true + }, + "created": { + "type": "string", + "format": "date", + "default": "2018-01-01" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "default": "2018-01-02T23:58:43.066-05:00" + }, + "bytes": { + "type": "string", + "format": "byte", + "default": "c3dhZ2dlciBjb2RlZ2Vu" + }, + "binary": { + "type": "string", + "example": "binary", + "default": "DEADBEEF" + } + } + } + } +} diff --git a/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes.sh b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes.sh new file mode 100755 index 00000000000..75362d2b93d --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/integrationtests/scala/client/required-attributes.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -eo pipefail + +declare prefix="required-attributes" + +declare opts="-DdebugModels -DdebugOperations -Dproject -Dmodels -Dapis -DapiTests=false -DapiDocs=false -DmodelTests=false -DmodelDocs=false $JAVA_OPTS" +declare curdir=$(cd $(dirname "${BASH_SOURCE}") && pwd) + +# NOTE: This is sensitive to the location of this script. +declare clijar=${SWAGGER_CODEGEN_CLI_JAR:-$(cd $curdir && cd ../../../../../../../swagger-codegen-cli/target/ && echo $PWD)/swagger-codegen-cli.jar} + +exec \java ${opts} -jar ${clijar} generate \ + -i ${prefix}-spec.json -l scala \ + -o ${prefix}-expected; diff --git a/samples/client/petstore/scala/.swagger-codegen/VERSION b/samples/client/petstore/scala/.swagger-codegen/VERSION index cc6612c36e0..50794f17f1a 100644 --- a/samples/client/petstore/scala/.swagger-codegen/VERSION +++ b/samples/client/petstore/scala/.swagger-codegen/VERSION @@ -1 +1 @@ -2.3.0 \ No newline at end of file +2.3.1-SNAPSHOT \ No newline at end of file diff --git a/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/PetApi.scala b/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/PetApi.scala index 7ee97e2ae40..4763f547e1a 100644 --- a/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/PetApi.scala +++ b/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/PetApi.scala @@ -26,6 +26,7 @@ import javax.ws.rs.core.MediaType import java.io.File import java.util.Date +import java.util.TimeZone import scala.collection.mutable.HashMap @@ -49,9 +50,18 @@ class PetApi( val defBasePath: String = "http://petstore.swagger.io/v2", defApiInvoker: ApiInvoker = ApiInvoker ) { - + private lazy val dateTimeFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + private val dateFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } implicit val formats = new org.json4s.DefaultFormats { - override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+0000") + override def dateFormatter = dateTimeFormatter } implicit val stringReader: ClientResponseReader[String] = ClientResponseReaders.StringReader implicit val unitReader: ClientResponseReader[Unit] = ClientResponseReaders.UnitReader diff --git a/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/StoreApi.scala b/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/StoreApi.scala index 1d467b4c97b..5c2d86cd962 100644 --- a/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/StoreApi.scala +++ b/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/StoreApi.scala @@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType import java.io.File import java.util.Date +import java.util.TimeZone import scala.collection.mutable.HashMap @@ -47,9 +48,18 @@ class StoreApi( val defBasePath: String = "http://petstore.swagger.io/v2", defApiInvoker: ApiInvoker = ApiInvoker ) { - + private lazy val dateTimeFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + private val dateFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } implicit val formats = new org.json4s.DefaultFormats { - override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+0000") + override def dateFormatter = dateTimeFormatter } implicit val stringReader: ClientResponseReader[String] = ClientResponseReaders.StringReader implicit val unitReader: ClientResponseReader[Unit] = ClientResponseReaders.UnitReader diff --git a/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/UserApi.scala b/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/UserApi.scala index e223e9287b0..cd8690b7c3b 100644 --- a/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/UserApi.scala +++ b/samples/client/petstore/scala/src/main/scala/io/swagger/client/api/UserApi.scala @@ -24,6 +24,7 @@ import javax.ws.rs.core.MediaType import java.io.File import java.util.Date +import java.util.TimeZone import scala.collection.mutable.HashMap @@ -47,9 +48,18 @@ class UserApi( val defBasePath: String = "http://petstore.swagger.io/v2", defApiInvoker: ApiInvoker = ApiInvoker ) { - + private lazy val dateTimeFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } + private val dateFormatter = { + val formatter = new SimpleDateFormat("yyyy-MM-dd") + formatter.setTimeZone(TimeZone.getTimeZone("UTC")) + formatter + } implicit val formats = new org.json4s.DefaultFormats { - override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+0000") + override def dateFormatter = dateTimeFormatter } implicit val stringReader: ClientResponseReader[String] = ClientResponseReaders.StringReader implicit val unitReader: ClientResponseReader[Unit] = ClientResponseReaders.UnitReader