Fixes issues with Scalaz outputs (#4508)

Scalaz is not re-generated by users or CI (only verified), so some
compilation issues have been introduced into the generator.
Specifically, the generator previously didn't handle defaults well (or
even correctly, maybe?). This broke when array models were added to the
openapi document used to generate samples. The inclusion of Date-related
mappings being mapped to joda time in DefaultCodegen also caused some
issues with new DateTime properties on models.

Over the course of what appears to be Nov 10-17 2019, CircleCI seems to
be having intermittent issues with Scalaz verification. I found that
green builds were picking up SBT 0.13.x and failed builds were SBT
1.1.0. It's not clear where the system level SBT is being defined, but a
simple fix has been to enforce the sbt version in the generator.

For those unfamiliar with SBT; the SBT command acts as a launcher script
which may switch to older/newer versions of SBT. A Scala project invoked
with SBT 1.1.0 will look for an sbt.version override and happily attempt
compilation with the available SBT 1.1.0. The problem is that SBT 1.1.0
uses Scala 2.12 and this is not binary compatible with Scala 2.11. This
can cause issues with builds due to plugins or incompatible Java version.
This commit is contained in:
Jim Schubert 2019-11-17 09:18:57 -05:00 committed by GitHub
parent bf6db1c2d1
commit 10f17877f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 113 additions and 35 deletions

View File

@ -1086,6 +1086,7 @@ public class DefaultCodegen implements CodegenConfig {
reservedWords = new HashSet<String>();
// TODO: Move Java specific import mappings out of DefaultCodegen.
importMapping = new HashMap<String, String>();
importMapping.put("BigDecimal", "java.math.BigDecimal");
importMapping.put("UUID", "java.util.UUID");

View File

@ -19,8 +19,11 @@ package org.openapitools.codegen.languages;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -63,6 +66,8 @@ public class ScalazClientCodegen extends AbstractScalaCodegen implements Codegen
additionalProperties.put("apiPackage", apiPackage);
// Explicitly defining bulid.properties helps guarantee our sample remains compilable against the embedded target 2.11 scala
supportingFiles.add(new SupportingFile("build.properties.mustache", "", "project/build.properties"));
supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
supportingFiles.add(new SupportingFile("dateTimeCodecs.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "DateTimeCodecs.scala"));
supportingFiles.add(new SupportingFile("HelperCodecs.mustache", (sourceFolder + File.separator + apiPackage).replace(".", File.separator), "HelperCodecs.scala"));
@ -71,10 +76,16 @@ public class ScalazClientCodegen extends AbstractScalaCodegen implements Codegen
importMapping.remove("List");
importMapping.remove("Set");
importMapping.remove("Map");
importMapping.put("Date", "java.util.Date");
importMapping.put("ListBuffer", "scala.collection.mutable.ListBuffer");
// Overrides defaults applied in DefaultCodegen which don't apply cleanly to Scala.
importMapping.put("Date", "java.util.Date");
importMapping.put("DateTime", "org.joda.time.DateTime");
importMapping.put("LocalDateTime", "org.joda.time.LocalDateTime");
importMapping.put("LocalDate", "org.joda.time.LocalDate");
importMapping.put("LocalTime", "org.joda.time.LocalTime");
typeMapping = new HashMap<String, String>();
typeMapping.put("enum", "NSString");
typeMapping.put("array", "List");
@ -179,6 +190,42 @@ public class ScalazClientCodegen extends AbstractScalaCodegen implements Codegen
}
@Override
public String toDefaultValue(Schema p) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
// comment out the following as the default value is no handled differently
if (ModelUtils.isBooleanSchema(p)) {
return null;
} else if (ModelUtils.isDateSchema(p)) {
return null;
} else if (ModelUtils.isDateTimeSchema(p)) {
return null;
} else if (ModelUtils.isNumberSchema(p)) {
return null;
} else if (ModelUtils.isIntegerSchema(p)) {
return null;
} else if (ModelUtils.isMapSchema(p)) {
String inner = getSchemaType(ModelUtils.getAdditionalProperties(p));
return "Map.empty[String, " + inner + "] ";
} else if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
String inner = getSchemaType(ap.getItems());
String collectionType = typeMapping.get("array");
// We assume that users would map these collections to a monoid with an identity function
// There's no reason to assume mutable structure here (which may make consumption more difficult)
return collectionType + ".empty[" + inner + "] ";
} else if (ModelUtils.isStringSchema(p)) {
return null;
} else {
return null;
}
}
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;

View File

@ -21,6 +21,9 @@ import scalaz.concurrent.Task
import HelperCodecs._
{{#imports}}import {{import}}
{{/imports}}
{{#operations}}
object {{classname}} {

View File

@ -0,0 +1 @@
sbt.version=0.13.15

View File

@ -7,6 +7,10 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
{{#imports}}import {{import}}
{{/imports}}
{{#models}}
{{#model}}
import {{classname}}._

View File

@ -1 +1 @@
3.0.0-SNAPSHOT
4.2.2-SNAPSHOT

View File

@ -0,0 +1 @@
sbt.version=0.13.15

View File

@ -7,6 +7,8 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
import ApiResponse._
case class ApiResponse (

View File

@ -7,6 +7,8 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
import Category._
case class Category (

View File

@ -7,6 +7,9 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
import org.joda.time.DateTime
import Order._
case class Order (

View File

@ -7,6 +7,8 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
import Pet._
case class Pet (

View File

@ -21,13 +21,17 @@ import scalaz.concurrent.Task
import HelperCodecs._
import org.openapitools.client.api.ApiResponse
import java.io.File
import org.openapitools.client.api.Pet
object PetApi {
val client = PooledHttp1Client()
def escape(value: String): String = URLEncoder.encode(value, "utf-8").replaceAll("\\+", "%20")
def addPet(host: String, pet: Pet): Task[Unit] = {
def addPet(host: String, body: Pet): Task[Unit] = {
val path = "/pet"
val httpMethod = Method.POST
@ -40,7 +44,7 @@ object PetApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(pet)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
@ -65,7 +69,7 @@ object PetApi {
} yield resp
}
def findPetsByStatus(host: String, status: List[String])(implicit statusQuery: QueryParam[List[String]]): Task[List[Pet]] = {
def findPetsByStatus(host: String, status: List[String] = List.empty[String] )(implicit statusQuery: QueryParam[List[String]]): Task[List[Pet]] = {
implicit val returnTypeDecoder: EntityDecoder[List[Pet]] = jsonOf[List[Pet]]
val path = "/pet/findByStatus"
@ -86,7 +90,7 @@ object PetApi {
} yield resp
}
def findPetsByTags(host: String, tags: List[String])(implicit tagsQuery: QueryParam[List[String]]): Task[List[Pet]] = {
def findPetsByTags(host: String, tags: List[String] = List.empty[String] )(implicit tagsQuery: QueryParam[List[String]]): Task[List[Pet]] = {
implicit val returnTypeDecoder: EntityDecoder[List[Pet]] = jsonOf[List[Pet]]
val path = "/pet/findByTags"
@ -128,7 +132,7 @@ object PetApi {
} yield resp
}
def updatePet(host: String, pet: Pet): Task[Unit] = {
def updatePet(host: String, body: Pet): Task[Unit] = {
val path = "/pet"
val httpMethod = Method.PUT
@ -141,7 +145,7 @@ object PetApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(pet)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
@ -194,7 +198,7 @@ class HttpServicePetApi(service: HttpService) {
def escape(value: String): String = URLEncoder.encode(value, "utf-8").replaceAll("\\+", "%20")
def addPet(pet: Pet): Task[Unit] = {
def addPet(body: Pet): Task[Unit] = {
val path = "/pet"
val httpMethod = Method.POST
@ -207,7 +211,7 @@ class HttpServicePetApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(pet)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
@ -232,7 +236,7 @@ class HttpServicePetApi(service: HttpService) {
} yield resp
}
def findPetsByStatus(status: List[String])(implicit statusQuery: QueryParam[List[String]]): Task[List[Pet]] = {
def findPetsByStatus(status: List[String] = List.empty[String] )(implicit statusQuery: QueryParam[List[String]]): Task[List[Pet]] = {
implicit val returnTypeDecoder: EntityDecoder[List[Pet]] = jsonOf[List[Pet]]
val path = "/pet/findByStatus"
@ -253,7 +257,7 @@ class HttpServicePetApi(service: HttpService) {
} yield resp
}
def findPetsByTags(tags: List[String])(implicit tagsQuery: QueryParam[List[String]]): Task[List[Pet]] = {
def findPetsByTags(tags: List[String] = List.empty[String] )(implicit tagsQuery: QueryParam[List[String]]): Task[List[Pet]] = {
implicit val returnTypeDecoder: EntityDecoder[List[Pet]] = jsonOf[List[Pet]]
val path = "/pet/findByTags"
@ -295,7 +299,7 @@ class HttpServicePetApi(service: HttpService) {
} yield resp
}
def updatePet(pet: Pet): Task[Unit] = {
def updatePet(body: Pet): Task[Unit] = {
val path = "/pet"
val httpMethod = Method.PUT
@ -308,7 +312,7 @@ class HttpServicePetApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(pet)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp

View File

@ -21,6 +21,8 @@ import scalaz.concurrent.Task
import HelperCodecs._
import org.openapitools.client.api.Order
object StoreApi {
val client = PooledHttp1Client()
@ -88,7 +90,7 @@ object StoreApi {
} yield resp
}
def placeOrder(host: String, order: Order): Task[Order] = {
def placeOrder(host: String, body: Order): Task[Order] = {
implicit val returnTypeDecoder: EntityDecoder[Order] = jsonOf[Order]
val path = "/store/order"
@ -103,7 +105,7 @@ object StoreApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(order)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.expect[Order](req)
} yield resp
@ -177,7 +179,7 @@ class HttpServiceStoreApi(service: HttpService) {
} yield resp
}
def placeOrder(order: Order): Task[Order] = {
def placeOrder(body: Order): Task[Order] = {
implicit val returnTypeDecoder: EntityDecoder[Order] = jsonOf[Order]
val path = "/store/order"
@ -192,7 +194,7 @@ class HttpServiceStoreApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(order)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.expect[Order](req)
} yield resp

View File

@ -7,6 +7,8 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
import Tag._
case class Tag (

View File

@ -7,6 +7,8 @@ import argonaut.DecodeJson._
import org.http4s.{EntityDecoder, EntityEncoder}
import org.http4s.argonaut._
import org.joda.time.DateTime
import User._
case class User (

View File

@ -21,13 +21,15 @@ import scalaz.concurrent.Task
import HelperCodecs._
import org.openapitools.client.api.User
object UserApi {
val client = PooledHttp1Client()
def escape(value: String): String = URLEncoder.encode(value, "utf-8").replaceAll("\\+", "%20")
def createUser(host: String, user: User): Task[Unit] = {
def createUser(host: String, body: User): Task[Unit] = {
val path = "/user"
val httpMethod = Method.POST
@ -40,13 +42,13 @@ object UserApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
}
def createUsersWithArrayInput(host: String, user: List[User]): Task[Unit] = {
def createUsersWithArrayInput(host: String, body: List[User]): Task[Unit] = {
val path = "/user/createWithArray"
val httpMethod = Method.POST
@ -59,13 +61,13 @@ object UserApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
}
def createUsersWithListInput(host: String, user: List[User]): Task[Unit] = {
def createUsersWithListInput(host: String, body: List[User]): Task[Unit] = {
val path = "/user/createWithList"
val httpMethod = Method.POST
@ -78,7 +80,7 @@ object UserApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
@ -164,7 +166,7 @@ object UserApi {
} yield resp
}
def updateUser(host: String, username: String, user: User): Task[Unit] = {
def updateUser(host: String, username: String, body: User): Task[Unit] = {
val path = "/user/{username}".replaceAll("\\{" + "username" + "\\}",escape(username.toString))
val httpMethod = Method.PUT
@ -177,7 +179,7 @@ object UserApi {
for {
uri <- Task.fromDisjunction(Uri.fromString(host + path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
@ -190,7 +192,7 @@ class HttpServiceUserApi(service: HttpService) {
def escape(value: String): String = URLEncoder.encode(value, "utf-8").replaceAll("\\+", "%20")
def createUser(user: User): Task[Unit] = {
def createUser(body: User): Task[Unit] = {
val path = "/user"
val httpMethod = Method.POST
@ -203,13 +205,13 @@ class HttpServiceUserApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
}
def createUsersWithArrayInput(user: List[User]): Task[Unit] = {
def createUsersWithArrayInput(body: List[User]): Task[Unit] = {
val path = "/user/createWithArray"
val httpMethod = Method.POST
@ -222,13 +224,13 @@ class HttpServiceUserApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
}
def createUsersWithListInput(user: List[User]): Task[Unit] = {
def createUsersWithListInput(body: List[User]): Task[Unit] = {
val path = "/user/createWithList"
val httpMethod = Method.POST
@ -241,7 +243,7 @@ class HttpServiceUserApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp
@ -327,7 +329,7 @@ class HttpServiceUserApi(service: HttpService) {
} yield resp
}
def updateUser(username: String, user: User): Task[Unit] = {
def updateUser(username: String, body: User): Task[Unit] = {
val path = "/user/{username}".replaceAll("\\{" + "username" + "\\}",escape(username.toString))
val httpMethod = Method.PUT
@ -340,7 +342,7 @@ class HttpServiceUserApi(service: HttpService) {
for {
uri <- Task.fromDisjunction(Uri.fromString(path))
uriWithParams = uri.copy(query = queryParams)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(user)
req = Request(method = httpMethod, uri = uriWithParams, headers = headers.put(contentType)).withBody(body)
resp <- client.fetch[Unit](req)(_ => Task.now(()))
} yield resp