diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java index 8e5c9ec22df9..c3266f72bb86 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java @@ -108,10 +108,6 @@ public ScalaSttpClientCodegen() { apiTemplateFiles.put("api.mustache", ".scala"); embeddedTemplateDir = templateDir = "scala-sttp"; - String jsonLibrary = JSON_LIBRARY_PROPERTY.getValue(additionalProperties); - - String jsonValueClass = "circe".equals(jsonLibrary) ? "io.circe.Json" : "org.json4s.JValue"; - additionalProperties.put(CodegenConstants.GROUP_ID, groupId); additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId); additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion); @@ -147,7 +143,7 @@ public ScalaSttpClientCodegen() { typeMapping.put("number", "Double"); typeMapping.put("decimal", "BigDecimal"); typeMapping.put("ByteArray", "Array[Byte]"); - typeMapping.put("AnyType", jsonValueClass); + typeMapping.put("AnyType", "Any"); instantiationTypes.put("array", "ListBuffer"); instantiationTypes.put("map", "Map"); @@ -166,6 +162,11 @@ public void processOpts() { apiPackage = PACKAGE_PROPERTY.getApiPackage(additionalProperties); modelPackage = PACKAGE_PROPERTY.getModelPackage(additionalProperties); + String jsonLibrary = JSON_LIBRARY_PROPERTY.getValue(additionalProperties); + String jsonValueClass = "circe".equals(jsonLibrary) ? "io.circe.Json" : "org.json4s.JValue"; + typeMapping.put("object", jsonValueClass); + typeMapping.put("AnyType", jsonValueClass); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt")); final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator); diff --git a/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache b/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache index 9a2c169cc853..bbac20f10325 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache @@ -7,7 +7,7 @@ object AdditionalTypeSerializers { import org.json4s.{Serializer, CustomSerializer, JNull, MappingException} import org.json4s.JsonAST.JString case object URISerializer extends CustomSerializer[URI]( _ => ( { - case JString(s) => + case JString(s) => try new URI(s) catch { case _: URISyntaxException => @@ -25,21 +25,51 @@ object AdditionalTypeSerializers { } {{/json4s}} {{#circe}} +import java.io.File +import java.nio.file.Files + trait AdditionalTypeSerializers { - import io.circe._ - - implicit final lazy val URIDecoder: Decoder[URI] = Decoder.decodeString.emap(string => - try Right(new URI(string)) - catch { - case _: URISyntaxException => - Left("String could not be parsed as a URI reference, it violates RFC 2396.") - case _: NullPointerException => - Left("String is null.") - } - ) - - implicit final lazy val URIEncoder: Encoder[URI] = new Encoder[URI] { - final def apply(a: URI): Json = Json.fromString(a.toString) + import io.circe._ + + implicit final lazy val URIDecoder: Decoder[URI] = Decoder.decodeString.emap(string => + try Right(new URI(string)) + catch { + case _: URISyntaxException => + Left("String could not be parsed as a URI reference, it violates RFC 2396.") + case _: NullPointerException => + Left("String is null.") + } + ) + + implicit final lazy val URIEncoder: Encoder[URI] = new Encoder[URI] { + final def apply(a: URI): Json = Json.fromString(a.toString) + } + + implicit final lazy val FileDecoder: Decoder[File] = Decoder[Array[Byte]].emap { bytes => + try { + val tmpFile = File.createTempFile("download", ".tmp") + Files.write(tmpFile.toPath, bytes) + Right(tmpFile) + } catch { + case e: Exception => Left(s"Failed to write binary content to file: ${e.getMessage}") + } + } + + implicit final lazy val FileEncoder: Encoder[File] = Encoder[Array[Byte]].contramap( + f => Files.readAllBytes(f.toPath) + ) + + implicit final lazy val AnyDecoder: Decoder[Any] = Decoder[Json].map(_.asInstanceOf[Any]) + + implicit final lazy val AnyEncoder: Encoder[Any] = Encoder.instance { + case json: Json => json + case b: Boolean => Json.fromBoolean(b) + case n: Int => Json.fromInt(n) + case n: Long => Json.fromLong(n) + case n: Double => Json.fromDoubleOrNull(n) + case n: BigDecimal => Json.fromBigDecimal(n) + case s: String => Json.fromString(s) + case other => Json.fromString(other.toString) } } {{/circe}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp/jsonSupport.mustache b/modules/openapi-generator/src/main/resources/scala-sttp/jsonSupport.mustache index 2dcb6109a860..ad452812c682 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp/jsonSupport.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp/jsonSupport.mustache @@ -42,10 +42,9 @@ object JsonSupport extends SttpJson4sApi { {{/json4s}} {{#circe}} import io.circe.{Decoder, Encoder} -import io.circe.generic.AutoDerivation import sttp.client3.circe.SttpCirceApi -object JsonSupport extends SttpCirceApi with AutoDerivation with DateSerializers with AdditionalTypeSerializers { +object JsonSupport extends SttpCirceApi with DateSerializers with AdditionalTypeSerializers { {{#models}} {{#model}} diff --git a/modules/openapi-generator/src/main/resources/scala-sttp/model.mustache b/modules/openapi-generator/src/main/resources/scala-sttp/model.mustache index ecece27a59a5..e584c94da3b1 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp/model.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp/model.mustache @@ -4,6 +4,11 @@ package {{package}} {{#imports}} import {{import}} {{/imports}} +{{#circe}} +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import {{invokerPackage}}.JsonSupport._ +{{/circe}} {{#models}} {{#model}} @@ -24,6 +29,40 @@ case class {{classname}}( {{{name}}}: {{^required}}Option[{{/required}}{{^isEnum}}{{dataType}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}Enums.{{datatypeWithEnum}}{{/isArray}}{{#isArray}}Seq[{{classname}}Enums.{{datatypeWithEnum}}]{{/isArray}}{{/isEnum}}{{^required}}] = None{{/required}}{{^-last}},{{/-last}} {{/vars}} ) +{{#circe}} +object {{classname}} { +{{#hasVars}} + implicit val encoder{{classname}}: Encoder[{{classname}}] = Encoder.instance { t => + Json.fromFields{ + Seq( + {{#vars}} + {{#required}}Some("{{baseName}}" -> t.{{{name}}}.asJson){{/required}}{{^required}}t.{{{name}}}.map(v => "{{baseName}}" -> v.asJson){{/required}}{{^-last}},{{/-last}} + {{/vars}} + ).flatten + } + } + implicit val decoder{{classname}}: Decoder[{{classname}}] = Decoder.instance { c => + for { + {{#vars}} + {{{name}}} <- c.downField("{{baseName}}").as[{{^required}}Option[{{/required}}{{^isEnum}}{{dataType}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}Enums.{{datatypeWithEnum}}{{/isArray}}{{#isArray}}Seq[{{classname}}Enums.{{datatypeWithEnum}}]{{/isArray}}{{/isEnum}}{{^required}}]{{/required}}] + {{/vars}} + } yield {{classname}}( + {{#vars}} + {{{name}}} = {{{name}}}{{^-last}},{{/-last}} + {{/vars}} + ) + } +{{/hasVars}} +{{^hasVars}} + implicit val encoder{{classname}}: Encoder[{{classname}}] = Encoder.instance { _ => + Json.fromFields(Seq.empty) + } + implicit val decoder{{classname}}: Decoder[{{classname}}] = Decoder.instance { _ => + Right({{classname}}()) + } +{{/hasVars}} +} +{{/circe}} {{/isEnum}} {{#isEnum}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java index c6f2150321bd..e5f24e8d722f 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java @@ -110,6 +110,69 @@ public void verifyApiKeyLocations() throws IOException { assertFileContains(path, ".cookie(\"apikey\", apiKeyCookie)"); } + @Test + public void verifyCirceSerdeWithMixedCaseFields() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/scala/mixed-case-fields.yaml", null, new ParseOptions()).getOpenAPI(); + + ScalaSttpClientCodegen codegen = new ScalaSttpClientCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put("jsonLibrary", "circe"); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "true"); + generator.opts(input).generate(); + + Path mixedCaseModelPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/MixedCaseModel.scala"); + + assertFileContains(mixedCaseModelPath, "firstName"); + assertFileContains(mixedCaseModelPath, "phoneNumber"); + assertFileContains(mixedCaseModelPath, "lastName"); + assertFileContains(mixedCaseModelPath, "zipCode"); + assertFileContains(mixedCaseModelPath, "address"); + + assertFileContains(mixedCaseModelPath, "\"first-name\""); + assertFileContains(mixedCaseModelPath, "\"phone_number\""); + assertFileContains(mixedCaseModelPath, "\"lastName\""); + assertFileContains(mixedCaseModelPath, "\"ZipCode\""); + assertFileContains(mixedCaseModelPath, "\"address\""); + + assertFileContains(mixedCaseModelPath, "c.downField(\"first-name\")"); + assertFileContains(mixedCaseModelPath, "c.downField(\"phone_number\")"); + assertFileContains(mixedCaseModelPath, "c.downField(\"ZipCode\")"); + + assertFileContains(mixedCaseModelPath, "object MixedCaseModel"); + assertFileContains(mixedCaseModelPath, "implicit val encoderMixedCaseModel"); + assertFileContains(mixedCaseModelPath, "implicit val decoderMixedCaseModel"); + + Path binaryModelPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/BinaryPayload.scala"); + assertFileContains(binaryModelPath, "data: Option[File]"); + assertFileContains(binaryModelPath, "metadata: Option[io.circe.Json]"); + assertFileContains(binaryModelPath, "c.downField(\"data\")"); + assertFileContains(binaryModelPath, "c.downField(\"metadata\")"); + assertFileContains(binaryModelPath, "implicit val encoderBinaryPayload"); + assertFileContains(binaryModelPath, "implicit val decoderBinaryPayload"); + + Path additionalSerializersPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala"); + assertFileContains(additionalSerializersPath, "FileDecoder"); + assertFileContains(additionalSerializersPath, "FileEncoder"); + assertFileContains(additionalSerializersPath, "AnyDecoder"); + assertFileContains(additionalSerializersPath, "AnyEncoder"); + } + @Test public void headerSerialization() throws IOException { File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); diff --git a/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml b/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml new file mode 100644 index 000000000000..6b78aa8fc08a --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml @@ -0,0 +1,38 @@ +openapi: 3.0.0 +info: + title: Mixed Case Test + version: 1.0.0 +paths: + /test: + get: + operationId: getTest + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/MixedCaseModel' +components: + schemas: + MixedCaseModel: + type: object + properties: + first-name: + type: string + phone_number: + type: string + lastName: + type: string + ZipCode: + type: string + address: + type: string + BinaryPayload: + type: object + properties: + data: + type: string + format: binary + metadata: + type: object diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala index 137dbc248fad..962c4a002f32 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala @@ -2,20 +2,50 @@ package org.openapitools.client.core import java.net.{ URI, URISyntaxException } +import java.io.File +import java.nio.file.Files + trait AdditionalTypeSerializers { - import io.circe._ - - implicit final lazy val URIDecoder: Decoder[URI] = Decoder.decodeString.emap(string => - try Right(new URI(string)) - catch { - case _: URISyntaxException => - Left("String could not be parsed as a URI reference, it violates RFC 2396.") - case _: NullPointerException => - Left("String is null.") - } - ) - - implicit final lazy val URIEncoder: Encoder[URI] = new Encoder[URI] { - final def apply(a: URI): Json = Json.fromString(a.toString) + import io.circe._ + + implicit final lazy val URIDecoder: Decoder[URI] = Decoder.decodeString.emap(string => + try Right(new URI(string)) + catch { + case _: URISyntaxException => + Left("String could not be parsed as a URI reference, it violates RFC 2396.") + case _: NullPointerException => + Left("String is null.") + } + ) + + implicit final lazy val URIEncoder: Encoder[URI] = new Encoder[URI] { + final def apply(a: URI): Json = Json.fromString(a.toString) + } + + implicit final lazy val FileDecoder: Decoder[File] = Decoder[Array[Byte]].emap { bytes => + try { + val tmpFile = File.createTempFile("download", ".tmp") + Files.write(tmpFile.toPath, bytes) + Right(tmpFile) + } catch { + case e: Exception => Left(s"Failed to write binary content to file: ${e.getMessage}") + } + } + + implicit final lazy val FileEncoder: Encoder[File] = Encoder[Array[Byte]].contramap( + f => Files.readAllBytes(f.toPath) + ) + + implicit final lazy val AnyDecoder: Decoder[Any] = Decoder[Json].map(_.asInstanceOf[Any]) + + implicit final lazy val AnyEncoder: Encoder[Any] = Encoder.instance { + case json: Json => json + case b: Boolean => Json.fromBoolean(b) + case n: Int => Json.fromInt(n) + case n: Long => Json.fromLong(n) + case n: Double => Json.fromDoubleOrNull(n) + case n: BigDecimal => Json.fromBigDecimal(n) + case s: String => Json.fromString(s) + case other => Json.fromString(other.toString) } } diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala index 4d3dfa527521..3f6dfee76235 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala @@ -13,10 +13,9 @@ package org.openapitools.client.core import org.openapitools.client.model._ import io.circe.{Decoder, Encoder} -import io.circe.generic.AutoDerivation import sttp.client3.circe.SttpCirceApi -object JsonSupport extends SttpCirceApi with AutoDerivation with DateSerializers with AdditionalTypeSerializers { +object JsonSupport extends SttpCirceApi with DateSerializers with AdditionalTypeSerializers { implicit val EnumTestSearchDecoder: Decoder[EnumTestEnums.Search] = Decoder.decodeEnumeration(EnumTestEnums.Search) implicit val EnumTestSearchEncoder: Encoder[EnumTestEnums.Search] = Encoder.encodeEnumeration(EnumTestEnums.Search) diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala index b0abb512265e..514f416b07a1 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala @@ -11,6 +11,9 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ /** * An uploaded response @@ -21,4 +24,26 @@ case class ApiResponse( `type`: Option[String] = None, message: Option[String] = None ) +object ApiResponse { + implicit val encoderApiResponse: Encoder[ApiResponse] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.code.map(v => "code" -> v.asJson), + t.`type`.map(v => "type" -> v.asJson), + t.message.map(v => "message" -> v.asJson) + ).flatten + } + } + implicit val decoderApiResponse: Decoder[ApiResponse] = Decoder.instance { c => + for { + code <- c.downField("code").as[Option[Int]] + `type` <- c.downField("type").as[Option[String]] + message <- c.downField("message").as[Option[String]] + } yield ApiResponse( + code = code, + `type` = `type`, + message = message + ) + } +} diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Category.scala index 169acc49cefd..c03cc7fc6a1f 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Category.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Category.scala @@ -11,6 +11,9 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ /** * Pet category @@ -20,4 +23,23 @@ case class Category( id: Option[Long] = None, name: Option[String] = None ) +object Category { + implicit val encoderCategory: Encoder[Category] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.id.map(v => "id" -> v.asJson), + t.name.map(v => "name" -> v.asJson) + ).flatten + } + } + implicit val decoderCategory: Decoder[Category] = Decoder.instance { c => + for { + id <- c.downField("id").as[Option[Long]] + name <- c.downField("name").as[Option[String]] + } yield Category( + id = id, + name = name + ) + } +} diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/EnumTest.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/EnumTest.scala index 96fad75f85df..27de86d3402d 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/EnumTest.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/EnumTest.scala @@ -11,12 +11,37 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ case class EnumTest( emails: Option[Seq[String]] = None, search: Option[EnumTestEnums.Search] = None, sortBy: Option[Seq[EnumTestEnums.SortBy]] = None ) +object EnumTest { + implicit val encoderEnumTest: Encoder[EnumTest] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.emails.map(v => "emails" -> v.asJson), + t.search.map(v => "search" -> v.asJson), + t.sortBy.map(v => "sort_by" -> v.asJson) + ).flatten + } + } + implicit val decoderEnumTest: Decoder[EnumTest] = Decoder.instance { c => + for { + emails <- c.downField("emails").as[Option[Seq[String]]] + search <- c.downField("search").as[Option[EnumTestEnums.Search]] + sortBy <- c.downField("sort_by").as[Option[Seq[EnumTestEnums.SortBy]]] + } yield EnumTest( + emails = emails, + search = search, + sortBy = sortBy + ) + } +} object EnumTestEnums { diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Order.scala index 9e805e256b21..a3bf7ea39dcf 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Order.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Order.scala @@ -12,6 +12,9 @@ package org.openapitools.client.model import java.time.OffsetDateTime +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ /** * Pet Order @@ -26,6 +29,37 @@ case class Order( status: Option[OrderEnums.Status] = None, complete: Option[Boolean] = None ) +object Order { + implicit val encoderOrder: Encoder[Order] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.id.map(v => "id" -> v.asJson), + t.petId.map(v => "petId" -> v.asJson), + t.quantity.map(v => "quantity" -> v.asJson), + t.shipDate.map(v => "shipDate" -> v.asJson), + t.status.map(v => "status" -> v.asJson), + t.complete.map(v => "complete" -> v.asJson) + ).flatten + } + } + implicit val decoderOrder: Decoder[Order] = Decoder.instance { c => + for { + id <- c.downField("id").as[Option[Long]] + petId <- c.downField("petId").as[Option[Long]] + quantity <- c.downField("quantity").as[Option[Int]] + shipDate <- c.downField("shipDate").as[Option[OffsetDateTime]] + status <- c.downField("status").as[Option[OrderEnums.Status]] + complete <- c.downField("complete").as[Option[Boolean]] + } yield Order( + id = id, + petId = petId, + quantity = quantity, + shipDate = shipDate, + status = status, + complete = complete + ) + } +} object OrderEnums { diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Pet.scala index 3cbab6051284..e9f8b4b2eb61 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Pet.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Pet.scala @@ -11,6 +11,9 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ /** * a Pet @@ -25,6 +28,37 @@ case class Pet( /* pet status in the store */ status: Option[PetEnums.Status] = None ) +object Pet { + implicit val encoderPet: Encoder[Pet] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.id.map(v => "id" -> v.asJson), + t.category.map(v => "category" -> v.asJson), + Some("name" -> t.name.asJson), + Some("photoUrls" -> t.photoUrls.asJson), + t.tags.map(v => "tags" -> v.asJson), + t.status.map(v => "status" -> v.asJson) + ).flatten + } + } + implicit val decoderPet: Decoder[Pet] = Decoder.instance { c => + for { + id <- c.downField("id").as[Option[Long]] + category <- c.downField("category").as[Option[Category]] + name <- c.downField("name").as[String] + photoUrls <- c.downField("photoUrls").as[Seq[String]] + tags <- c.downField("tags").as[Option[Seq[Tag]]] + status <- c.downField("status").as[Option[PetEnums.Status]] + } yield Pet( + id = id, + category = category, + name = name, + photoUrls = photoUrls, + tags = tags, + status = status + ) + } +} object PetEnums { diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/PropertyNameMapping.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/PropertyNameMapping.scala index 3782f4c12be3..cd870e77f4e9 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/PropertyNameMapping.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/PropertyNameMapping.scala @@ -11,6 +11,9 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ case class PropertyNameMapping( `httpDebugOperation`: Option[String] = None, @@ -18,4 +21,29 @@ case class PropertyNameMapping( `type`: Option[String] = None, `typeWithUnderscore`: Option[String] = None ) +object PropertyNameMapping { + implicit val encoderPropertyNameMapping: Encoder[PropertyNameMapping] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.`httpDebugOperation`.map(v => "http_debug_operation" -> v.asJson), + t.`underscoreType`.map(v => "_type" -> v.asJson), + t.`type`.map(v => "type" -> v.asJson), + t.`typeWithUnderscore`.map(v => "type_" -> v.asJson) + ).flatten + } + } + implicit val decoderPropertyNameMapping: Decoder[PropertyNameMapping] = Decoder.instance { c => + for { + `httpDebugOperation` <- c.downField("http_debug_operation").as[Option[String]] + `underscoreType` <- c.downField("_type").as[Option[String]] + `type` <- c.downField("type").as[Option[String]] + `typeWithUnderscore` <- c.downField("type_").as[Option[String]] + } yield PropertyNameMapping( + `httpDebugOperation` = `httpDebugOperation`, + `underscoreType` = `underscoreType`, + `type` = `type`, + `typeWithUnderscore` = `typeWithUnderscore` + ) + } +} diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Tag.scala index c2020246658a..d1b9289b90cf 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Tag.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/Tag.scala @@ -11,6 +11,9 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ /** * Pet Tag @@ -20,4 +23,23 @@ case class Tag( id: Option[Long] = None, name: Option[String] = None ) +object Tag { + implicit val encoderTag: Encoder[Tag] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.id.map(v => "id" -> v.asJson), + t.name.map(v => "name" -> v.asJson) + ).flatten + } + } + implicit val decoderTag: Decoder[Tag] = Decoder.instance { c => + for { + id <- c.downField("id").as[Option[Long]] + name <- c.downField("name").as[Option[String]] + } yield Tag( + id = id, + name = name + ) + } +} diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/User.scala index 6977180bccee..339936997b69 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/User.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/model/User.scala @@ -11,6 +11,9 @@ */ package org.openapitools.client.model +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ +import org.openapitools.client.core.JsonSupport._ /** * a User @@ -27,4 +30,41 @@ case class User( /* User Status */ userStatus: Option[Int] = None ) +object User { + implicit val encoderUser: Encoder[User] = Encoder.instance { t => + Json.fromFields{ + Seq( + t.id.map(v => "id" -> v.asJson), + t.username.map(v => "username" -> v.asJson), + t.firstName.map(v => "firstName" -> v.asJson), + t.lastName.map(v => "lastName" -> v.asJson), + t.email.map(v => "email" -> v.asJson), + t.password.map(v => "password" -> v.asJson), + t.phone.map(v => "phone" -> v.asJson), + t.userStatus.map(v => "userStatus" -> v.asJson) + ).flatten + } + } + implicit val decoderUser: Decoder[User] = Decoder.instance { c => + for { + id <- c.downField("id").as[Option[Long]] + username <- c.downField("username").as[Option[String]] + firstName <- c.downField("firstName").as[Option[String]] + lastName <- c.downField("lastName").as[Option[String]] + email <- c.downField("email").as[Option[String]] + password <- c.downField("password").as[Option[String]] + phone <- c.downField("phone").as[Option[String]] + userStatus <- c.downField("userStatus").as[Option[Int]] + } yield User( + id = id, + username = username, + firstName = firstName, + lastName = lastName, + email = email, + password = password, + phone = phone, + userStatus = userStatus + ) + } +} diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala index 2ac2a4b19275..2e254a1b67ae 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala @@ -6,7 +6,7 @@ object AdditionalTypeSerializers { import org.json4s.{Serializer, CustomSerializer, JNull, MappingException} import org.json4s.JsonAST.JString case object URISerializer extends CustomSerializer[URI]( _ => ( { - case JString(s) => + case JString(s) => try new URI(s) catch { case _: URISyntaxException =>