toAdd = new ArrayList<>();
+ for (CodegenProperty parentProp : parentOwnProps) {
+ if (!existingPropNames.contains(parentProp.baseName)) {
+ toAdd.add(parentProp.clone());
+ }
+ }
+
+ if (!toAdd.isEmpty()) {
+ toAdd.addAll(childModel.allVars);
+ childModel.allVars = toAdd;
+ }
+ }
+
/**
* Update/clean up model imports
*
@@ -349,6 +578,14 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, Listjavadoc}}
{{/javadocRenderer}}
- def {{operationId}}({{>methodParameters}}): Request[{{#separateErrorChannel}}Either[ResponseException[String, Exception], {{>operationReturnType}}]{{/separateErrorChannel}}{{^separateErrorChannel}}{{>operationReturnType}}{{/separateErrorChannel}}] =
+ def {{operationId}}({{>methodParameters}}): Request[{{#separateErrorChannel}}Either[ResponseException[String], {{>operationReturnType}}]{{/separateErrorChannel}}{{^separateErrorChannel}}{{>operationReturnType}}{{/separateErrorChannel}}] =
basicRequest
.method(Method.{{httpMethod.toUpperCase}}, uri"$baseUrl{{{path}}}{{#queryParams.0}}?{{/queryParams.0}}{{#queryParams}}{{baseName}}=${ {{paramName}} }{{^-last}}&{{/-last}}{{/queryParams}}{{#authMethods}}{{#isApiKey}}{{#isKeyInQuery}}{{#queryParams.0}}&{{/queryParams.0}}{{^queryParams.0}}?{{/queryParams.0}}{{keyParamName}}=${apiKeyQuery}{{/isKeyInQuery}}{{/isApiKey}}{{/authMethods}}")
.contentType({{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{^consumes}}"application/json"{{/consumes}}){{#headerParams}}
@@ -31,11 +31,11 @@ class {{classname}}(baseUrl: String) {
.cookie("{{keyParamName}}", apiKeyCookie){{/isKeyInCookie}}{{/isApiKey}}{{/authMethods}}{{#formParams.0}}{{^isMultipart}}
.body(Map({{#formParams}}
{{>paramFormCreation}}{{^-last}},{{/-last}}{{/formParams}}
- )){{/isMultipart}}{{#isMultipart}}
+ ).collect { case (k, Some(v)) => k -> v }){{/isMultipart}}{{#isMultipart}}
.multipartBody(Seq({{#formParams}}
{{>paramMultipartCreation}}{{^-last}}, {{/-last}}{{/formParams}}
).flatten){{/isMultipart}}{{/formParams.0}}{{#bodyParam}}
- .body({{paramName}}){{/bodyParam}}
+ .body(asJson({{paramName}})){{/bodyParam}}
.response({{#separateErrorChannel}}{{^returnType}}asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))){{/returnType}}{{#returnType}}asJson[{{>operationReturnType}}]{{/returnType}}{{/separateErrorChannel}}{{^separateErrorChannel}}{{^returnType}}asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))).getRight{{/returnType}}{{#returnType}}asJson[{{>operationReturnType}}].getRight{{/returnType}}{{/separateErrorChannel}})
{{/operation}}
diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/build.sbt.mustache
index d915432ab27a..ceedaa92bfc7 100644
--- a/modules/openapi-generator/src/main/resources/scala-sttp4/build.sbt.mustache
+++ b/modules/openapi-generator/src/main/resources/scala-sttp4/build.sbt.mustache
@@ -2,7 +2,7 @@ version := "{{artifactVersion}}"
name := "{{artifactId}}"
organization := "{{groupId}}"
-scalaVersion := "2.13.16"
+scalaVersion := "2.13.18"
crossScalaVersions := Seq(scalaVersion.value, "2.12.20")
libraryDependencies ++= Seq(
@@ -15,7 +15,9 @@ libraryDependencies ++= Seq(
"org.json4s" %% "json4s-jackson" % "{{json4sVersion}}"
{{/json4s}}
{{#circe}}
- "com.softwaremill.sttp.client4" %% "circe" % "{{sttpClientVersion}}"
+ "com.softwaremill.sttp.client4" %% "circe" % "{{sttpClientVersion}}",
+ "io.circe" %% "circe-generic" % "{{circeVersion}}",
+ "io.circe" %% "circe-generic-extras" % "{{circeExtrasVersion}}",
{{/circe}}
)
diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/jsonSupport.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/jsonSupport.mustache
index d8f929c43225..e280f87cca4f 100644
--- a/modules/openapi-generator/src/main/resources/scala-sttp4/jsonSupport.mustache
+++ b/modules/openapi-generator/src/main/resources/scala-sttp4/jsonSupport.mustache
@@ -7,34 +7,11 @@ import {{modelPackage}}._
{{#json4s}}
import org.json4s._
import sttp.client4.json4s.SttpJson4sApi
-import scala.reflect.ClassTag
object JsonSupport extends SttpJson4sApi {
def enumSerializers: Seq[Serializer[_]] = Seq[Serializer[_]](){{#models}}{{#model}}{{#isEnum}} :+
- new EnumNameSerializer({{classname}}){{/isEnum}}{{#hasEnums}}{{#vars}}{{#isEnum}} :+
- new EnumNameSerializer({{classname}}Enums.{{datatypeWithEnum}}){{/isEnum}}{{/vars}}{{/hasEnums}}{{/model}}{{/models}}
-
- private class EnumNameSerializer[E <: Enumeration: ClassTag](enumeration: E) extends Serializer[E#Value] {
- import JsonDSL._
- val EnumerationClass: Class[E#Value] = classOf[E#Value]
-
- def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), E#Value] = {
- case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) =>
- json match {
- case JString(value) => enumeration.withName(value)
- case value => throw new MappingException(s"Can't convert $value to $EnumerationClass")
- }
- }
-
- private[this] def isValid(json: JValue) = json match {
- case JString(value) if enumeration.values.exists(_.toString == value) => true
- case _ => false
- }
-
- def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
- case i: E#Value => i.toString
- }
- }
+ {{classname}}.{{classname}}Serializer{{/isEnum}}{{#hasEnums}}{{#vars}}{{#isEnum}} :+
+ {{classname}}Enums.{{datatypeWithEnum}}.{{datatypeWithEnum}}Serializer{{/isEnum}}{{/vars}}{{/hasEnums}}{{/model}}{{/models}}
implicit val format: Formats = DefaultFormats ++ enumSerializers ++ DateSerializers.all ++ AdditionalTypeSerializers.all
implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization
@@ -43,17 +20,9 @@ object JsonSupport extends SttpJson4sApi {
{{#circe}}
import io.circe.{Decoder, Encoder}
import io.circe.generic.AutoDerivation
-import sttp.client3.circe.SttpCirceApi
+import sttp.client4.circe.SttpCirceApi
object JsonSupport extends SttpCirceApi with AutoDerivation with DateSerializers with AdditionalTypeSerializers {
-
-{{#models}}
-{{#model}}
-{{#isEnum}}
- implicit val {{classname}}Decoder: Decoder[{{classname}}.{{classname}}] = Decoder.decodeEnumeration({{classname}})
- implicit val {{classname}}Encoder: Encoder[{{classname}}.{{classname}}] = Encoder.encodeEnumeration({{classname}})
-{{/isEnum}}
-{{/model}}
-{{/models}}
+ // Enum encoders/decoders are defined in their respective companion objects
}
{{/circe}}
diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/methodParameters.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/methodParameters.mustache
index 0f941eefee32..b735a87b18ce 100644
--- a/modules/openapi-generator/src/main/resources/scala-sttp4/methodParameters.mustache
+++ b/modules/openapi-generator/src/main/resources/scala-sttp4/methodParameters.mustache
@@ -1 +1 @@
-{{#authMethods.0}}{{#authMethods}}{{#isApiKey}}{{#isKeyInHeader}}apiKeyHeader: String{{/isKeyInHeader}}{{#isKeyInQuery}}apiKeyQuery: String{{/isKeyInQuery}}{{#isKeyInCookie}}apiKeyCookie: String{{/isKeyInCookie}}{{/isApiKey}}{{#isBasic}}{{#isBasicBasic}}username: String, password: String{{/isBasicBasic}}{{#isBasicBearer}}bearerToken: String{{/isBasicBearer}}{{/isBasic}}{{^-last}}, {{/-last}}{{/authMethods}})({{/authMethods.0}}{{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}{{#isContainer}}{{dataType}}{{/isContainer}}{{^isContainer}}Option[{{dataType}}]{{/isContainer}}{{/required}}{{^defaultValue}}{{^required}}{{^isContainer}} = None{{/isContainer}}{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}
\ No newline at end of file
+{{#authMethods.0}}{{#authMethods}}{{#isApiKey}}{{#isKeyInHeader}}apiKeyHeader: String{{/isKeyInHeader}}{{#isKeyInQuery}}apiKeyQuery: String{{/isKeyInQuery}}{{#isKeyInCookie}}apiKeyCookie: String{{/isKeyInCookie}}{{/isApiKey}}{{#isBasic}}{{#isBasicBasic}}username: String, password: String{{/isBasicBasic}}{{#isBasicBearer}}bearerToken: String{{/isBasicBearer}}{{/isBasic}}{{^-last}}, {{/-last}}{{/authMethods}})({{/authMethods.0}}{{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}Option[{{dataType}}]{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allParams}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache
index ecece27a59a5..2bd35516ff20 100644
--- a/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache
+++ b/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache
@@ -7,6 +7,11 @@ import {{import}}
{{#models}}
{{#model}}
+{{#vendorExtensions.x-isOneOfMember}}
+// This case class is defined inline in {{vendorExtensions.x-oneOfParent}}.scala
+// This file is intentionally minimal to avoid duplication.
+{{/vendorExtensions.x-isOneOfMember}}
+{{^vendorExtensions.x-isOneOfMember}}
{{#description}}
{{#javadocRenderer}}
{{#title}}
@@ -15,6 +20,178 @@ import {{import}}
{{{description}}}
{{/javadocRenderer}}
{{/description}}
+{{#vendorExtensions.x-isSealedTrait}}
+{{#vendorExtensions.x-hasParentProps}}
+sealed trait {{classname}} {
+ {{#vendorExtensions.x-parentProps}}
+ def {{{name}}}: {{^required}}Option[{{/required}}{{dataType}}{{^required}}]{{/required}}
+ {{/vendorExtensions.x-parentProps}}
+}
+{{/vendorExtensions.x-hasParentProps}}
+{{^vendorExtensions.x-hasParentProps}}
+sealed trait {{classname}}
+{{/vendorExtensions.x-hasParentProps}}
+
+{{! Generate inline case classes for oneOf members }}
+{{#vendorExtensions.x-oneOfMembers}}
+case class {{classname}}(
+ {{#allVars}}
+ {{#description}}
+ /* {{{.}}} */
+ {{/description}}
+ {{{name}}}: {{^required}}Option[{{/required}}{{dataType}}{{^required}}] = None{{/required}}{{^-last}},{{/-last}}
+ {{/allVars}}
+ {{^allVars}}
+ {{! Empty case class for models with no properties }}
+ {{/allVars}}
+) extends {{vendorExtensions.x-oneOfParent}}
+{{#circe}}
+object {{classname}} {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[{{classname}}] = deriveEncoder
+ implicit val decoder: Decoder[{{classname}}] = deriveDecoder
+}
+{{/circe}}
+
+{{/vendorExtensions.x-oneOfMembers}}
+object {{classname}} {
+{{#json4s}}
+ import org.json4s._
+
+{{^vendorExtensions.x-use-discr}}
+ // oneOf without discriminator - json4s custom serializer
+ implicit object {{classname}}Serializer extends Serializer[{{classname}}] {
+ def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), {{classname}}] = {
+ case (TypeInfo(clazz, _), json) if classOf[{{classname}}].isAssignableFrom(clazz) =>
+ // Try each oneOf type in order
+ {{#oneOf}}
+ Extraction.extract[{{.}}](json) match {
+ case x: {{.}} => return x
+ case _ => // continue
+ }
+ {{/oneOf}}
+ throw new MappingException(s"Can't convert $json to {{classname}}")
+ }
+
+ def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
+ {{#oneOf}}
+ case x: {{.}} => Extraction.decompose(x)
+ {{/oneOf}}
+ }
+ }
+{{/vendorExtensions.x-use-discr}}
+{{#vendorExtensions.x-use-discr}}
+ // oneOf with discriminator
+ implicit object {{classname}}Serializer extends Serializer[{{classname}}] {
+ def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), {{classname}}] = {
+ case (TypeInfo(clazz, _), json) if classOf[{{classname}}].isAssignableFrom(clazz) =>
+ (json \ "{{discriminator.propertyName}}") match {
+ {{#oneOf}}
+ case JString("{{.}}") => Extraction.extract[{{.}}](json)
+ {{/oneOf}}
+ case _ => throw new MappingException(s"Unknown discriminator value in $json")
+ }
+ }
+
+ def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
+ {{#oneOf}}
+ case x: {{.}} => Extraction.decompose(x).merge(JObject("{{discriminator.propertyName}}" -> JString("{{.}}")))
+ {{/oneOf}}
+ }
+ }
+{{/vendorExtensions.x-use-discr}}
+{{/json4s}}
+{{#circe}}
+{{^vendorExtensions.x-use-discr}}
+ // oneOf without discriminator - using semiauto derivation
+ import io.circe.{Encoder, Decoder}
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[{{classname}}] = deriveEncoder
+ implicit val decoder: Decoder[{{classname}}] = deriveDecoder
+{{/vendorExtensions.x-use-discr}}
+{{#vendorExtensions.x-use-discr}}
+ // oneOf with discriminator - using semiauto derivation with Configuration
+ import io.circe.{Encoder, Decoder}
+ import io.circe.generic.extras._
+ import io.circe.generic.extras.semiauto._
+
+ private implicit val config: Configuration = Configuration.default.withDiscriminator("{{discriminator.propertyName}}")
+ .copy(
+ transformConstructorNames = {
+{{#vendorExtensions.x-oneOfMembers}}
+ case "{{classname}}" => "{{vendorExtensions.x-discriminator-value}}"
+{{/vendorExtensions.x-oneOfMembers}}
+ case other => sys.error(s"Invalid {{classname}} discriminant: ${other}")
+ }
+ )
+ implicit val encoder: Encoder[{{classname}}] = deriveConfiguredEncoder
+ implicit val decoder: Decoder[{{classname}}] = deriveConfiguredDecoder
+{{/vendorExtensions.x-use-discr}}
+{{/circe}}
+}
+{{/vendorExtensions.x-isSealedTrait}}
+{{#vendorExtensions.x-isEnum}}
+sealed trait {{classname}}
+
+object {{classname}} {
+{{#allowableValues}}
+ {{#values}}
+ case object {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} extends {{classname}}
+ {{/values}}
+{{/allowableValues}}
+
+{{#circe}}
+ import io.circe.{Encoder, Decoder}
+
+ implicit val encoder: Encoder[{{classname}}] = Encoder.encodeString.contramap[{{classname}}] {
+{{#allowableValues}}
+ {{#values}}
+ case {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} => "{{.}}"
+ {{/values}}
+{{/allowableValues}}
+ }
+
+ implicit val decoder: Decoder[{{classname}}] = Decoder.decodeString.emap {
+{{#allowableValues}}
+ {{#values}}
+ case "{{.}}" => Right({{#fnEnumEntry}}{{.}}{{/fnEnumEntry}})
+ {{/values}}
+{{/allowableValues}}
+ case other => Left(s"Invalid {{classname}}: $other")
+ }
+{{/circe}}
+{{#json4s}}
+ import org.json4s._
+
+ implicit object {{classname}}Serializer extends Serializer[{{classname}}] {
+ def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), {{classname}}] = {
+ case (TypeInfo(clazz, _), json) if classOf[{{classname}}].isAssignableFrom(clazz) =>
+ json match {
+{{#allowableValues}}
+ {{#values}}
+ case JString("{{.}}") => {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}}
+ {{/values}}
+{{/allowableValues}}
+ case other => throw new MappingException(s"Invalid {{classname}}: $other")
+ }
+ }
+
+ def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
+{{#allowableValues}}
+ {{#values}}
+ case {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} => JString("{{.}}")
+ {{/values}}
+{{/allowableValues}}
+ }
+ }
+{{/json4s}}
+}
+{{/vendorExtensions.x-isEnum}}
+{{#vendorExtensions.x-isRegularModel}}
{{^isEnum}}
case class {{classname}}(
{{#vars}}
@@ -23,38 +200,72 @@ case class {{classname}}(
{{/description}}
{{{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}}
-)
+){{#parent}} extends {{parent}}{{/parent}}
{{/isEnum}}
+{{#circe}}
+object {{classname}} {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
-{{#isEnum}}
-object {{classname}} extends Enumeration {
- type {{classname}} = {{classname}}.Value
-{{#allowableValues}}
- {{#values}}
- val {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} = Value("{{.}}")
- {{/values}}
-{{/allowableValues}}
+ implicit val encoder: Encoder[{{classname}}] = deriveEncoder
+ implicit val decoder: Decoder[{{classname}}] = deriveDecoder
}
-{{/isEnum}}
+{{/circe}}
{{#hasEnums}}
object {{classname}}Enums {
-
- {{#vars}}
- {{#isEnum}}
- type {{datatypeWithEnum}} = {{datatypeWithEnum}}.Value
- {{/isEnum}}
- {{/vars}}
{{#vars}}
{{#isEnum}}
- object {{datatypeWithEnum}} extends Enumeration {
+
+ sealed trait {{datatypeWithEnum}}
+ object {{datatypeWithEnum}} {
{{#_enum}}
- val {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} = Value("{{.}}")
+ case object {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} extends {{datatypeWithEnum}}
{{/_enum}}
- }
+{{#circe}}
+ import io.circe.{Encoder, Decoder}
+
+ implicit val encoder: Encoder[{{datatypeWithEnum}}] = Encoder.encodeString.contramap[{{datatypeWithEnum}}] {
+{{#_enum}}
+ case {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} => "{{.}}"
+{{/_enum}}
+ }
+
+ implicit val decoder: Decoder[{{datatypeWithEnum}}] = Decoder.decodeString.emap {
+{{#_enum}}
+ case "{{.}}" => Right({{#fnEnumEntry}}{{.}}{{/fnEnumEntry}})
+{{/_enum}}
+ case other => Left(s"Invalid {{datatypeWithEnum}}: $other")
+ }
+{{/circe}}
+{{#json4s}}
+ import org.json4s._
+
+ implicit object {{datatypeWithEnum}}Serializer extends Serializer[{{datatypeWithEnum}}] {
+ def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), {{datatypeWithEnum}}] = {
+ case (TypeInfo(clazz, _), json) if classOf[{{datatypeWithEnum}}].isAssignableFrom(clazz) =>
+ json match {
+{{#_enum}}
+ case JString("{{.}}") => {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}}
+{{/_enum}}
+ case other => throw new MappingException(s"Invalid {{datatypeWithEnum}}: $other")
+ }
+ }
+
+ def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
+{{#_enum}}
+ case {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} => JString("{{.}}")
+{{/_enum}}
+ }
+ }
+{{/json4s}}
+ }
{{/isEnum}}
{{/vars}}
}
{{/hasEnums}}
+{{/vendorExtensions.x-isRegularModel}}
+{{/vendorExtensions.x-isOneOfMember}}
{{/model}}
{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/paramCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/paramCreation.mustache
index 25ec73e8d5e1..be9104b3995d 100644
--- a/modules/openapi-generator/src/main/resources/scala-sttp4/paramCreation.mustache
+++ b/modules/openapi-generator/src/main/resources/scala-sttp4/paramCreation.mustache
@@ -1 +1 @@
-"{{baseName}}", {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}.toString
\ No newline at end of file
+"{{baseName}}", {{#isContainer}}{{#required}}{{{paramName}}}.mkString(","){{/required}}{{^required}}{{{paramName}}}.filter(_.nonEmpty).map(_.mkString(",")){{/required}}{{/isContainer}}{{^isContainer}}{{#required}}{{{paramName}}}.toString{{/required}}{{^required}}{{{paramName}}}{{/required}}{{/isContainer}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/paramFormCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/paramFormCreation.mustache
index a0f1a65c0746..27291ec817e9 100644
--- a/modules/openapi-generator/src/main/resources/scala-sttp4/paramFormCreation.mustache
+++ b/modules/openapi-generator/src/main/resources/scala-sttp4/paramFormCreation.mustache
@@ -1 +1 @@
-"{{baseName}}" -> {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}
\ No newline at end of file
+"{{baseName}}" -> {{#required}}Some({{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}){{/required}}{{^required}}{{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}{{/required}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java
index 2e74ec6de7eb..a819f7401bf9 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java
@@ -19,6 +19,7 @@
import java.nio.file.Paths;
import static org.openapitools.codegen.TestUtils.assertFileContains;
+import static org.openapitools.codegen.TestUtils.assertFileNotContains;
public class Sttp4CodegenTest {
@@ -54,6 +55,231 @@ public void verifyApiKeyLocations() throws IOException {
assertFileContains(path, ".cookie(\"apikey\", apiKeyCookie)");
}
+ @Test
+ public void verifyOneOfSupportWithCirce() 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/sttp4-oneOf.yaml", null, new ParseOptions()).getOpenAPI();
+
+ ScalaSttp4ClientCodegen codegen = new ScalaSttp4ClientCodegen();
+ 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, "false");
+ generator.opts(input).generate();
+
+ // Test oneOf without discriminator generates sealed trait with semiauto
+ Path petPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Pet.scala");
+ assertFileContains(petPath, "sealed trait Pet");
+ assertFileContains(petPath, "object Pet {");
+ assertFileContains(petPath, "import io.circe.generic.semiauto._");
+ assertFileContains(petPath, "// oneOf without discriminator - using semiauto derivation");
+ assertFileContains(petPath, "implicit val encoder: Encoder[Pet] = deriveEncoder");
+ assertFileContains(petPath, "implicit val decoder: Decoder[Pet] = deriveDecoder");
+
+ // Test oneOf with discriminator uses semiauto with Configuration
+ Path animalPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Animal.scala");
+ assertFileContains(animalPath, "sealed trait Animal");
+ assertFileContains(animalPath, "object Animal {");
+ assertFileContains(animalPath, "import io.circe.generic.extras.semiauto._");
+ assertFileContains(animalPath, "// oneOf with discriminator - using semiauto derivation with Configuration");
+ assertFileContains(animalPath,
+ "private implicit val config: Configuration = Configuration.default.withDiscriminator(\"petType\")");
+ assertFileContains(animalPath, "implicit val encoder: Encoder[Animal] = deriveConfiguredEncoder");
+ assertFileContains(animalPath, "implicit val decoder: Decoder[Animal] = deriveConfiguredDecoder");
+
+ // Test oneOf with discriminator mapping
+ Path vehiclePath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Vehicle.scala");
+ assertFileContains(vehiclePath, "sealed trait Vehicle");
+ assertFileContains(vehiclePath, "object Vehicle {");
+ assertFileContains(vehiclePath, "// oneOf with discriminator - using semiauto derivation with Configuration");
+ assertFileContains(vehiclePath,
+ "private implicit val config: Configuration = Configuration.default.withDiscriminator(\"vehicleType\")");
+ assertFileContains(vehiclePath, "\"Car\" => \"car\"");
+ assertFileContains(vehiclePath, "\"Truck\" => \"truck\"");
+
+ // Verify regular models are still case classes
+ Path dogPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Dog.scala");
+ assertFileContains(dogPath, "case class Dog(");
+ assertFileContains(dogPath, "name: String");
+ assertFileContains(dogPath, "breed: String");
+ }
+
+ @Test
+ public void verifyOneOfSupportWithJson4s() 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/sttp4-oneOf.yaml", null, new ParseOptions()).getOpenAPI();
+
+ ScalaSttp4ClientCodegen codegen = new ScalaSttp4ClientCodegen();
+ codegen.setOutputDir(output.getAbsolutePath());
+ codegen.additionalProperties().put("jsonLibrary", "json4s");
+
+ 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, "false");
+ generator.opts(input).generate();
+
+ // Test oneOf without discriminator generates sealed trait with json4s
+ Path petPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Pet.scala");
+ assertFileContains(petPath, "sealed trait Pet");
+ assertFileContains(petPath, "object Pet {");
+ assertFileContains(petPath, "import org.json4s._");
+ assertFileContains(petPath, "// oneOf without discriminator - json4s custom serializer");
+ assertFileContains(petPath, "implicit object PetSerializer extends Serializer[Pet]");
+ assertFileContains(petPath, "Extraction.extract[Dog](json)");
+ assertFileContains(petPath, "Extraction.extract[Cat](json)");
+
+ // Test oneOf with discriminator
+ Path animalPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Animal.scala");
+ assertFileContains(animalPath, "sealed trait Animal");
+ assertFileContains(animalPath, "// oneOf with discriminator");
+ assertFileContains(animalPath, "petType");
+ }
+
+ @Test
+ public void verifyOneOfWithEmptyMembers() 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/sttp4-oneOf-empty-members.yaml", null, new ParseOptions())
+ .getOpenAPI();
+
+ ScalaSttp4ClientCodegen codegen = new ScalaSttp4ClientCodegen();
+ 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, "false");
+ generator.opts(input).generate();
+
+ // Test sealed trait is generated correctly
+ Path eventPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Event.scala");
+ assertFileContains(eventPath, "sealed trait Event");
+
+ // Test empty case classes (no properties except discriminator which was
+ // removed)
+ assertFileContains(eventPath, "case class ClickEvent(\n) extends Event");
+ assertFileContains(eventPath, "case class ViewEvent(\n) extends Event");
+
+ // Test case class with properties (PurchaseEvent has amount)
+ assertFileContains(eventPath, "case class PurchaseEvent(");
+ assertFileContains(eventPath, "amount: Double");
+
+ // Verify discriminator is configured
+ assertFileContains(eventPath, "Configuration.default.withDiscriminator(\"eventType\")");
+
+ // Verify the discriminator property was removed from inline members
+ // ClickEvent and ViewEvent should have NO properties at all
+ assertFileContains(eventPath, "case class ClickEvent(\n) extends Event");
+ assertFileContains(eventPath, "case class ViewEvent(\n) extends Event");
+ }
+
+ @Test
+ public void verifyOneOfWithParentProperties() 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/sttp4-oneOf-with-parent-props.yaml", null,
+ new ParseOptions())
+ .getOpenAPI();
+
+ ScalaSttp4ClientCodegen codegen = new ScalaSttp4ClientCodegen();
+ 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, "false");
+ generator.opts(input).generate();
+
+ Path parentPath = Paths.get(
+ outputPath + "/src/main/scala/org/openapitools/client/model/Parent.scala");
+ String content = Files.readString(parentPath);
+
+ // Sealed trait exists with abstract member for parent property
+ assertFileContains(parentPath, "sealed trait Parent {");
+ assertFileContains(parentPath, "def createdAt: OffsetDateTime");
+
+ // ChildOne exists (empty child is NOT dropped) and extends Parent
+ assertFileContains(parentPath, "case class ChildOne(");
+ assertFileContains(parentPath, ") extends Parent");
+
+ // ChildOne has parent property propagated
+ int childOneStart = content.indexOf("case class ChildOne(");
+ int childOneEnd = content.indexOf(") extends Parent", childOneStart);
+ Assert.assertTrue(childOneStart >= 0 && childOneEnd >= 0,
+ "ChildOne case class should exist");
+ String childOneBlock = content.substring(childOneStart, childOneEnd);
+ Assert.assertTrue(childOneBlock.contains("createdAt: OffsetDateTime"),
+ "ChildOne should have parent property 'createdAt'");
+
+ // ChildOne does NOT have sibling properties
+ Assert.assertFalse(childOneBlock.contains("status"),
+ "ChildOne should NOT contain sibling property 'status'");
+ Assert.assertFalse(childOneBlock.contains("detail"),
+ "ChildOne should NOT contain sibling property 'detail'");
+
+ // ChildTwo has parent property + its own properties
+ assertFileContains(parentPath, "case class ChildTwo(");
+ int childTwoStart = content.indexOf("case class ChildTwo(");
+ int childTwoEnd = content.indexOf(") extends Parent", childTwoStart);
+ Assert.assertTrue(childTwoStart >= 0 && childTwoEnd >= 0,
+ "ChildTwo case class should exist");
+ String childTwoBlock = content.substring(childTwoStart, childTwoEnd);
+ Assert.assertTrue(childTwoBlock.contains("createdAt: OffsetDateTime"),
+ "ChildTwo should have parent property 'createdAt'");
+ Assert.assertTrue(childTwoBlock.contains("status: String"),
+ "ChildTwo should have its own property 'status'");
+ Assert.assertTrue(childTwoBlock.contains("detail: Option[Nested]"),
+ "ChildTwo should have its own property 'detail'");
+ }
+
@Test
public void verifyDateTimeLocalGeneratesLocalDateTime() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
diff --git a/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf-empty-members.yaml b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf-empty-members.yaml
new file mode 100644
index 000000000000..3d2be6e556db
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf-empty-members.yaml
@@ -0,0 +1,61 @@
+openapi: 3.0.0
+info:
+ title: OneOf Empty Members Test
+ version: 1.0.0
+paths:
+ /event:
+ post:
+ operationId: sendEvent
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Event'
+ responses:
+ '200':
+ description: Success
+components:
+ schemas:
+ Event:
+ oneOf:
+ - $ref: '#/components/schemas/ClickEvent'
+ - $ref: '#/components/schemas/ViewEvent'
+ - $ref: '#/components/schemas/PurchaseEvent'
+ discriminator:
+ propertyName: eventType
+ mapping:
+ click: '#/components/schemas/ClickEvent'
+ view: '#/components/schemas/ViewEvent'
+ purchase: '#/components/schemas/PurchaseEvent'
+
+ ClickEvent:
+ type: object
+ required:
+ - eventType
+ properties:
+ eventType:
+ type: string
+ const: click
+
+ ViewEvent:
+ type: object
+ required:
+ - eventType
+ properties:
+ eventType:
+ type: string
+ const: view
+
+ PurchaseEvent:
+ type: object
+ required:
+ - eventType
+ - amount
+ properties:
+ eventType:
+ type: string
+ const: purchase
+ amount:
+ type: number
+ description: Purchase amount
diff --git a/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf-with-parent-props.yaml b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf-with-parent-props.yaml
new file mode 100644
index 000000000000..79332ad0fd03
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf-with-parent-props.yaml
@@ -0,0 +1,59 @@
+openapi: 3.0.0
+info:
+ title: OneOf with Parent Properties Test
+ version: 1.0.0
+paths:
+ /parents:
+ get:
+ operationId: getParent
+ responses:
+ '200':
+ description: A parent object
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Parent'
+components:
+ schemas:
+ Nested:
+ type: object
+ properties:
+ amount:
+ type: number
+ currency:
+ type: string
+
+ Parent:
+ title: Parent
+ description: "A parent type with shared properties and oneOf children"
+ required:
+ - created_at
+ properties:
+ created_at:
+ description: "The creation timestamp."
+ type: string
+ format: date-time
+ oneOf:
+ - $ref: '#/components/schemas/ChildOne'
+ - $ref: '#/components/schemas/ChildTwo'
+
+ ChildOne:
+ title: ChildOne
+ description: "First child - intentionally has no properties"
+
+ ChildTwo:
+ title: ChildTwo
+ description: "Second child with its own properties"
+ required:
+ - status
+ properties:
+ status:
+ type: string
+ description: "The status of this child."
+ enum:
+ - active
+ - inactive
+ detail:
+ allOf:
+ - $ref: '#/components/schemas/Nested'
+ description: "Nested detail object"
diff --git a/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf.yaml b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf.yaml
new file mode 100644
index 000000000000..480996ce2d06
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-oneOf.yaml
@@ -0,0 +1,107 @@
+openapi: 3.0.0
+info:
+ title: OneOf Test API
+ version: 1.0.0
+paths:
+ /pets:
+ get:
+ operationId: getPet
+ responses:
+ '200':
+ description: A pet
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Pet'
+components:
+ schemas:
+ Dog:
+ type: object
+ required:
+ - petType
+ - name
+ - breed
+ properties:
+ petType:
+ type: string
+ name:
+ type: string
+ breed:
+ type: string
+ barkVolume:
+ type: integer
+
+ Cat:
+ type: object
+ required:
+ - petType
+ - name
+ - age
+ properties:
+ petType:
+ type: string
+ name:
+ type: string
+ age:
+ type: integer
+ indoor:
+ type: boolean
+
+ # OneOf without discriminator
+ Pet:
+ oneOf:
+ - $ref: '#/components/schemas/Dog'
+ - $ref: '#/components/schemas/Cat'
+
+ # OneOf with discriminator
+ Animal:
+ oneOf:
+ - $ref: '#/components/schemas/Dog'
+ - $ref: '#/components/schemas/Cat'
+ discriminator:
+ propertyName: petType
+
+ # OneOf with discriminator and mapping
+ Vehicle:
+ oneOf:
+ - $ref: '#/components/schemas/Car'
+ - $ref: '#/components/schemas/Truck'
+ discriminator:
+ propertyName: vehicleType
+ mapping:
+ car: '#/components/schemas/Car'
+ truck: '#/components/schemas/Truck'
+
+ Car:
+ type: object
+ required:
+ - vehicleType
+ - make
+ - model
+ properties:
+ vehicleType:
+ type: string
+ const: car
+ make:
+ type: string
+ model:
+ type: string
+ doors:
+ type: integer
+
+ Truck:
+ type: object
+ required:
+ - vehicleType
+ - make
+ - model
+ properties:
+ vehicleType:
+ type: string
+ const: truck
+ make:
+ type: string
+ model:
+ type: string
+ bedLength:
+ type: number
diff --git a/samples/client/petstore/scala-sttp4-circe/.openapi-generator-ignore b/samples/client/petstore/scala-sttp4-circe/.openapi-generator-ignore
new file mode 100644
index 000000000000..7484ee590a38
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/samples/client/petstore/scala-sttp4-circe/.openapi-generator/FILES b/samples/client/petstore/scala-sttp4-circe/.openapi-generator/FILES
new file mode 100644
index 000000000000..93bed4b07d22
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/.openapi-generator/FILES
@@ -0,0 +1,15 @@
+README.md
+build.sbt
+project/build.properties
+src/main/scala/org/openapitools/client/api/PetApi.scala
+src/main/scala/org/openapitools/client/api/StoreApi.scala
+src/main/scala/org/openapitools/client/api/UserApi.scala
+src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala
+src/main/scala/org/openapitools/client/core/DateSerializers.scala
+src/main/scala/org/openapitools/client/core/JsonSupport.scala
+src/main/scala/org/openapitools/client/model/ApiResponse.scala
+src/main/scala/org/openapitools/client/model/Category.scala
+src/main/scala/org/openapitools/client/model/Order.scala
+src/main/scala/org/openapitools/client/model/Pet.scala
+src/main/scala/org/openapitools/client/model/Tag.scala
+src/main/scala/org/openapitools/client/model/User.scala
diff --git a/samples/client/petstore/scala-sttp4-circe/.openapi-generator/VERSION b/samples/client/petstore/scala-sttp4-circe/.openapi-generator/VERSION
new file mode 100644
index 000000000000..0610c66bc14f
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/.openapi-generator/VERSION
@@ -0,0 +1 @@
+7.21.0-SNAPSHOT
diff --git a/samples/client/petstore/scala-sttp4-circe/README.md b/samples/client/petstore/scala-sttp4-circe/README.md
new file mode 100644
index 000000000000..381d1643e766
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/README.md
@@ -0,0 +1,117 @@
+# openapi-client
+
+OpenAPI Petstore
+- API version: 1.0.0
+ - Generator version: 7.21.0-SNAPSHOT
+
+This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+
+
+*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)*
+
+## Requirements
+
+Building the API client library requires:
+1. Java 1.7+
+2. Maven/Gradle/SBT
+
+## Installation
+
+To install the API client library to your local Maven repository, simply execute:
+
+```shell
+mvn clean install
+```
+
+To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:
+
+```shell
+mvn clean deploy
+```
+
+Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information.
+
+### Maven users
+
+Add this dependency to your project's POM:
+
+```xml
+
+ org.openapitools
+ openapi-client
+ 1.0.0
+ compile
+
+```
+
+### Gradle users
+
+Add this dependency to your project's build file:
+
+```groovy
+compile "org.openapitools:openapi-client:1.0.0"
+```
+
+### SBT users
+
+```scala
+libraryDependencies += "org.openapitools" % "openapi-client" % "1.0.0"
+```
+
+## Getting Started
+
+## Documentation for API Endpoints
+
+All URIs are relative to *http://petstore.swagger.io/v2*
+
+Class | Method | HTTP request | Description
+------------ | ------------- | ------------- | -------------
+*PetApi* | **addPet** | **POST** /pet | Add a new pet to the store
+*PetApi* | **deletePet** | **DELETE** /pet/${petId} | Deletes a pet
+*PetApi* | **findPetsByStatus** | **GET** /pet/findByStatus | Finds Pets by status
+*PetApi* | **findPetsByTags** | **GET** /pet/findByTags | Finds Pets by tags
+*PetApi* | **getPetById** | **GET** /pet/${petId} | Find pet by ID
+*PetApi* | **updatePet** | **PUT** /pet | Update an existing pet
+*PetApi* | **updatePetWithForm** | **POST** /pet/${petId} | Updates a pet in the store with form data
+*PetApi* | **uploadFile** | **POST** /pet/${petId}/uploadImage | uploads an image
+*StoreApi* | **deleteOrder** | **DELETE** /store/order/${orderId} | Delete purchase order by ID
+*StoreApi* | **getInventory** | **GET** /store/inventory | Returns pet inventories by status
+*StoreApi* | **getOrderById** | **GET** /store/order/${orderId} | Find purchase order by ID
+*StoreApi* | **placeOrder** | **POST** /store/order | Place an order for a pet
+*UserApi* | **createUser** | **POST** /user | Create user
+*UserApi* | **createUsersWithArrayInput** | **POST** /user/createWithArray | Creates list of users with given input array
+*UserApi* | **createUsersWithListInput** | **POST** /user/createWithList | Creates list of users with given input array
+*UserApi* | **deleteUser** | **DELETE** /user/${username} | Delete user
+*UserApi* | **getUserByName** | **GET** /user/${username} | Get user by user name
+*UserApi* | **loginUser** | **GET** /user/login | Logs user into the system
+*UserApi* | **logoutUser** | **GET** /user/logout | Logs out current logged in user session
+*UserApi* | **updateUser** | **PUT** /user/${username} | Updated user
+
+
+## Documentation for Models
+
+ - [ApiResponse](ApiResponse.md)
+ - [Category](Category.md)
+ - [Order](Order.md)
+ - [Pet](Pet.md)
+ - [Tag](Tag.md)
+ - [User](User.md)
+
+
+
+## Documentation for Authorization
+
+
+Authentication schemes defined for the API:
+
+ ### api_key
+
+ - **Type**: API key
+ - **API key parameter name**: api_key
+ - **Location**: HTTP header
+
+
+## Author
+
+
+
diff --git a/samples/client/petstore/scala-sttp4-circe/build.sbt b/samples/client/petstore/scala-sttp4-circe/build.sbt
new file mode 100644
index 000000000000..d298053de21d
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/build.sbt
@@ -0,0 +1,19 @@
+version := "1.0.0"
+name := "openapi-client"
+organization := "org.openapitools"
+
+scalaVersion := "2.13.18"
+crossScalaVersions := Seq(scalaVersion.value, "2.12.20")
+
+libraryDependencies ++= Seq(
+ "com.softwaremill.sttp.client4" %% "core" % "4.0.15",
+ "com.softwaremill.sttp.client4" %% "circe" % "4.0.15",
+ "io.circe" %% "circe-generic" % "0.14.15",
+ "io.circe" %% "circe-generic-extras" % "0.14.4",
+)
+
+scalacOptions := Seq(
+ "-unchecked",
+ "-deprecation",
+ "-feature"
+)
diff --git a/samples/client/petstore/scala-sttp4-circe/project/build.properties b/samples/client/petstore/scala-sttp4-circe/project/build.properties
new file mode 100644
index 000000000000..cc68b53f1a30
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/project/build.properties
@@ -0,0 +1 @@
+sbt.version=1.10.11
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/PetApi.scala
new file mode 100644
index 000000000000..23d94e4e0143
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/PetApi.scala
@@ -0,0 +1,169 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.api
+
+import org.openapitools.client.model.ApiResponse
+import java.io.File
+import org.openapitools.client.model.Pet
+import org.openapitools.client.core.JsonSupport._
+import sttp.client4._
+import sttp.model.Method
+
+object PetApi {
+ def apply(baseUrl: String = "http://petstore.swagger.io/v2") = new PetApi(baseUrl)
+}
+
+class PetApi(baseUrl: String) {
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 200 : Pet (successful operation)
+ * code 405 : (Invalid input)
+ *
+ * @param pet Pet object that needs to be added to the store
+ */
+ def addPet(pet: Pet): Request[Either[ResponseException[String], Pet]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/pet")
+ .contentType("application/json")
+ .body(asJson(pet))
+ .response(asJson[Pet])
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 400 : (Invalid pet value)
+ *
+ * @param petId Pet id to delete
+ * @param apiKey
+ */
+ def deletePet(petId: Long, apiKey: Option[String] = None): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.DELETE, uri"$baseUrl/pet/${petId}")
+ .contentType("application/json")
+ .header("api_key", apiKey)
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ * Multiple status values can be provided with comma separated strings
+ *
+ * Expected answers:
+ * code 200 : Seq[Pet] (successful operation)
+ * code 400 : (Invalid status value)
+ *
+ * @param status Status values that need to be considered for filter
+ */
+ def findPetsByStatus(status: Seq[String] = Seq.empty): Request[Either[ResponseException[String], Seq[Pet]]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/pet/findByStatus?status=${ status }")
+ .contentType("application/json")
+ .response(asJson[Seq[Pet]])
+
+ /**
+ * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
+ *
+ * Expected answers:
+ * code 200 : Seq[Pet] (successful operation)
+ * code 400 : (Invalid tag value)
+ *
+ * @param tags Tags to filter by
+ */
+ def findPetsByTags(tags: Seq[String] = Seq.empty): Request[Either[ResponseException[String], Seq[Pet]]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/pet/findByTags?tags=${ tags }")
+ .contentType("application/json")
+ .response(asJson[Seq[Pet]])
+
+ /**
+ * Returns a single pet
+ *
+ * Expected answers:
+ * code 200 : Pet (successful operation)
+ * code 400 : (Invalid ID supplied)
+ * code 404 : (Pet not found)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ *
+ * @param petId ID of pet to return
+ */
+ def getPetById(apiKeyHeader: String)(petId: Long): Request[Either[ResponseException[String], Pet]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/pet/${petId}")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .response(asJson[Pet])
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 200 : Pet (successful operation)
+ * code 400 : (Invalid ID supplied)
+ * code 404 : (Pet not found)
+ * code 405 : (Validation exception)
+ *
+ * @param pet Pet object that needs to be added to the store
+ */
+ def updatePet(pet: Pet): Request[Either[ResponseException[String], Pet]] =
+ basicRequest
+ .method(Method.PUT, uri"$baseUrl/pet")
+ .contentType("application/json")
+ .body(asJson(pet))
+ .response(asJson[Pet])
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 405 : (Invalid input)
+ *
+ * @param petId ID of pet that needs to be updated
+ * @param name Updated name of the pet
+ * @param status Updated status of the pet
+ */
+ def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/pet/${petId}")
+ .contentType("application/x-www-form-urlencoded")
+ .body(Map(
+ "name" -> name,
+ "status" -> status
+ ).collect { case (k, Some(v)) => k -> v })
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 200 : ApiResponse (successful operation)
+ *
+ * @param petId ID of pet to update
+ * @param additionalMetadata Additional data to pass to server
+ * @param file file to upload
+ */
+ def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): Request[Either[ResponseException[String], ApiResponse]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/pet/${petId}/uploadImage")
+ .contentType("multipart/form-data")
+ .multipartBody(Seq(
+ additionalMetadata.map(multipart("additionalMetadata", _))
+,
+ file.map(multipartFile("file", _))
+
+ ).flatten)
+ .response(asJson[ApiResponse])
+
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/StoreApi.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/StoreApi.scala
new file mode 100644
index 000000000000..27efb05281b1
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/StoreApi.scala
@@ -0,0 +1,88 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.api
+
+import org.openapitools.client.model.Order
+import org.openapitools.client.core.JsonSupport._
+import sttp.client4._
+import sttp.model.Method
+
+object StoreApi {
+ def apply(baseUrl: String = "http://petstore.swagger.io/v2") = new StoreApi(baseUrl)
+}
+
+class StoreApi(baseUrl: String) {
+
+ /**
+ * For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
+ *
+ * Expected answers:
+ * code 400 : (Invalid ID supplied)
+ * code 404 : (Order not found)
+ *
+ * @param orderId ID of the order that needs to be deleted
+ */
+ def deleteOrder(orderId: String): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.DELETE, uri"$baseUrl/store/order/${orderId}")
+ .contentType("application/json")
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ * Returns a map of status codes to quantities
+ *
+ * Expected answers:
+ * code 200 : Map[String, Int] (successful operation)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ */
+ def getInventory(apiKeyHeader: String)(): Request[Either[ResponseException[String], Map[String, Int]]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/store/inventory")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .response(asJson[Map[String, Int]])
+
+ /**
+ * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions
+ *
+ * Expected answers:
+ * code 200 : Order (successful operation)
+ * code 400 : (Invalid ID supplied)
+ * code 404 : (Order not found)
+ *
+ * @param orderId ID of pet that needs to be fetched
+ */
+ def getOrderById(orderId: Long): Request[Either[ResponseException[String], Order]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/store/order/${orderId}")
+ .contentType("application/json")
+ .response(asJson[Order])
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 200 : Order (successful operation)
+ * code 400 : (Invalid Order)
+ *
+ * @param order order placed for purchasing the pet
+ */
+ def placeOrder(order: Order): Request[Either[ResponseException[String], Order]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/store/order")
+ .contentType("application/json")
+ .body(asJson(order))
+ .response(asJson[Order])
+
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/UserApi.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/UserApi.scala
new file mode 100644
index 000000000000..176c21e2b316
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/api/UserApi.scala
@@ -0,0 +1,175 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.api
+
+import java.time.OffsetDateTime
+import org.openapitools.client.model.User
+import org.openapitools.client.core.JsonSupport._
+import sttp.client4._
+import sttp.model.Method
+
+object UserApi {
+ def apply(baseUrl: String = "http://petstore.swagger.io/v2") = new UserApi(baseUrl)
+}
+
+class UserApi(baseUrl: String) {
+
+ /**
+ * This can only be done by the logged in user.
+ *
+ * Expected answers:
+ * code 0 : (successful operation)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ *
+ * @param user Created user object
+ */
+ def createUser(apiKeyHeader: String)(user: User): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/user")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .body(asJson(user))
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 0 : (successful operation)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ *
+ * @param user List of user object
+ */
+ def createUsersWithArrayInput(apiKeyHeader: String)(user: Seq[User]): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/user/createWithArray")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .body(asJson(user))
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 0 : (successful operation)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ *
+ * @param user List of user object
+ */
+ def createUsersWithListInput(apiKeyHeader: String)(user: Seq[User]): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.POST, uri"$baseUrl/user/createWithList")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .body(asJson(user))
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ * This can only be done by the logged in user.
+ *
+ * Expected answers:
+ * code 400 : (Invalid username supplied)
+ * code 404 : (User not found)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ *
+ * @param username The name that needs to be deleted
+ */
+ def deleteUser(apiKeyHeader: String)(username: String): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.DELETE, uri"$baseUrl/user/${username}")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 200 : User (successful operation)
+ * code 400 : (Invalid username supplied)
+ * code 404 : (User not found)
+ *
+ * @param username The name that needs to be fetched. Use user1 for testing.
+ */
+ def getUserByName(username: String): Request[Either[ResponseException[String], User]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/user/${username}")
+ .contentType("application/json")
+ .response(asJson[User])
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 200 : String (successful operation)
+ * Headers :
+ * Set-Cookie - Cookie authentication key for use with the `api_key` apiKey authentication.
+ * X-Rate-Limit - calls per hour allowed by the user
+ * X-Expires-After - date in UTC when token expires
+ * code 400 : (Invalid username/password supplied)
+ *
+ * @param username The user name for login
+ * @param password The password for login in clear text
+ */
+ def loginUser(username: String, password: String): Request[Either[ResponseException[String], String]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/user/login?username=${ username }&password=${ password }")
+ .contentType("application/json")
+ .response(asJson[String])
+
+ /**
+ *
+ *
+ * Expected answers:
+ * code 0 : (successful operation)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ */
+ def logoutUser(apiKeyHeader: String)(): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.GET, uri"$baseUrl/user/logout")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+ /**
+ * This can only be done by the logged in user.
+ *
+ * Expected answers:
+ * code 400 : (Invalid user supplied)
+ * code 404 : (User not found)
+ *
+ * Available security schemes:
+ * api_key (apiKey)
+ *
+ * @param username name that need to be deleted
+ * @param user Updated user object
+ */
+ def updateUser(apiKeyHeader: String)(username: String, user: User): Request[Either[ResponseException[String], Unit]] =
+ basicRequest
+ .method(Method.PUT, uri"$baseUrl/user/${username}")
+ .contentType("application/json")
+ .header("api_key", apiKeyHeader)
+ .body(asJson(user))
+ .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
+
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala
new file mode 100644
index 000000000000..137dbc248fad
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala
@@ -0,0 +1,21 @@
+package org.openapitools.client.core
+
+import java.net.{ URI, URISyntaxException }
+
+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)
+ }
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/DateSerializers.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/DateSerializers.scala
new file mode 100644
index 000000000000..1bed2914651f
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/DateSerializers.scala
@@ -0,0 +1,13 @@
+package org.openapitools.client.core
+
+import java.time.{LocalDate, OffsetDateTime}
+import java.time.format.DateTimeFormatter
+
+trait DateSerializers {
+ import io.circe.{Decoder, Encoder}
+ implicit val isoOffsetDateTimeDecoder: Decoder[OffsetDateTime] = Decoder.decodeOffsetDateTimeWithFormatter(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
+ implicit val isoOffsetDateTimeEncoder: Encoder[OffsetDateTime] = Encoder.encodeOffsetDateTimeWithFormatter(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
+
+ implicit val localDateDecoder: Decoder[LocalDate] = Decoder.decodeLocalDateWithFormatter(DateTimeFormatter.ISO_LOCAL_DATE)
+ implicit val localDateEncoder: Encoder[LocalDate] = Encoder.encodeLocalDateWithFormatter(DateTimeFormatter.ISO_LOCAL_DATE)
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala
new file mode 100644
index 000000000000..dd7cd56e15e0
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/core/JsonSupport.scala
@@ -0,0 +1,21 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.core
+
+import org.openapitools.client.model._
+import io.circe.{Decoder, Encoder}
+import io.circe.generic.AutoDerivation
+import sttp.client4.circe.SttpCirceApi
+
+object JsonSupport extends SttpCirceApi with AutoDerivation with DateSerializers with AdditionalTypeSerializers {
+ // Enum encoders/decoders are defined in their respective companion objects
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala
new file mode 100644
index 000000000000..b1b1c90495a6
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala
@@ -0,0 +1,31 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.model
+
+
+ /**
+ * An uploaded response
+ * Describes the result of uploading an image resource
+ */
+case class ApiResponse(
+ code: Option[Int] = None,
+ `type`: Option[String] = None,
+ message: Option[String] = None
+)
+object ApiResponse {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[ApiResponse] = deriveEncoder
+ implicit val decoder: Decoder[ApiResponse] = deriveDecoder
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala
new file mode 100644
index 000000000000..def67cc526f2
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala
@@ -0,0 +1,30 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.model
+
+
+ /**
+ * Pet category
+ * A category for a pet
+ */
+case class Category(
+ id: Option[Long] = None,
+ name: Option[String] = None
+)
+object Category {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[Category] = deriveEncoder
+ implicit val decoder: Decoder[Category] = deriveDecoder
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala
new file mode 100644
index 000000000000..6cb20a7a07cd
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala
@@ -0,0 +1,60 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.model
+
+import java.time.OffsetDateTime
+
+ /**
+ * Pet Order
+ * An order for a pets from the pet store
+ */
+case class Order(
+ id: Option[Long] = None,
+ petId: Option[Long] = None,
+ quantity: Option[Int] = None,
+ shipDate: Option[OffsetDateTime] = None,
+ /* Order Status */
+ status: Option[OrderEnums.Status] = None,
+ complete: Option[Boolean] = None
+)
+object Order {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[Order] = deriveEncoder
+ implicit val decoder: Decoder[Order] = deriveDecoder
+}
+object OrderEnums {
+
+ sealed trait Status
+ object Status {
+ case object Placed extends Status
+ case object Approved extends Status
+ case object Delivered extends Status
+
+ import io.circe.{Encoder, Decoder}
+
+ implicit val encoder: Encoder[Status] = Encoder.encodeString.contramap[Status] {
+ case Placed => "placed"
+ case Approved => "approved"
+ case Delivered => "delivered"
+ }
+
+ implicit val decoder: Decoder[Status] = Decoder.decodeString.emap {
+ case "placed" => Right(Placed)
+ case "approved" => Right(Approved)
+ case "delivered" => Right(Delivered)
+ case other => Left(s"Invalid Status: $other")
+ }
+ }
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala
new file mode 100644
index 000000000000..88f0ce279b25
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala
@@ -0,0 +1,59 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.model
+
+
+ /**
+ * a Pet
+ * A pet for sale in the pet store
+ */
+case class Pet(
+ id: Option[Long] = None,
+ category: Option[Category] = None,
+ name: String,
+ photoUrls: Seq[String],
+ tags: Option[Seq[Tag]] = None,
+ /* pet status in the store */
+ status: Option[PetEnums.Status] = None
+)
+object Pet {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[Pet] = deriveEncoder
+ implicit val decoder: Decoder[Pet] = deriveDecoder
+}
+object PetEnums {
+
+ sealed trait Status
+ object Status {
+ case object Available extends Status
+ case object Pending extends Status
+ case object Sold extends Status
+
+ import io.circe.{Encoder, Decoder}
+
+ implicit val encoder: Encoder[Status] = Encoder.encodeString.contramap[Status] {
+ case Available => "available"
+ case Pending => "pending"
+ case Sold => "sold"
+ }
+
+ implicit val decoder: Decoder[Status] = Decoder.decodeString.emap {
+ case "available" => Right(Available)
+ case "pending" => Right(Pending)
+ case "sold" => Right(Sold)
+ case other => Left(s"Invalid Status: $other")
+ }
+ }
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala
new file mode 100644
index 000000000000..4672a3316f36
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala
@@ -0,0 +1,30 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.model
+
+
+ /**
+ * Pet Tag
+ * A tag for a pet
+ */
+case class Tag(
+ id: Option[Long] = None,
+ name: Option[String] = None
+)
+object Tag {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[Tag] = deriveEncoder
+ implicit val decoder: Decoder[Tag] = deriveDecoder
+}
diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala
new file mode 100644
index 000000000000..f488d72e97d1
--- /dev/null
+++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala
@@ -0,0 +1,37 @@
+/**
+ * OpenAPI Petstore
+ * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+package org.openapitools.client.model
+
+
+ /**
+ * a User
+ * A User who is purchasing from the pet store
+ */
+case class User(
+ id: Option[Long] = None,
+ username: Option[String] = None,
+ firstName: Option[String] = None,
+ lastName: Option[String] = None,
+ email: Option[String] = None,
+ password: Option[String] = None,
+ phone: Option[String] = None,
+ /* User Status */
+ userStatus: Option[Int] = None
+)
+object User {
+ import io.circe._
+ import io.circe.syntax._
+ import io.circe.generic.semiauto._
+
+ implicit val encoder: Encoder[User] = deriveEncoder
+ implicit val decoder: Decoder[User] = deriveDecoder
+}
diff --git a/samples/client/petstore/scala-sttp4/build.sbt b/samples/client/petstore/scala-sttp4/build.sbt
index 9ad901170074..47e47be09c4e 100644
--- a/samples/client/petstore/scala-sttp4/build.sbt
+++ b/samples/client/petstore/scala-sttp4/build.sbt
@@ -2,12 +2,12 @@ version := "1.0.0"
name := "openapi-client"
organization := "org.openapitools"
-scalaVersion := "2.13.16"
+scalaVersion := "2.13.18"
crossScalaVersions := Seq(scalaVersion.value, "2.12.20")
libraryDependencies ++= Seq(
- "com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1",
- "com.softwaremill.sttp.client4" %% "json4s" % "4.0.0-M1",
+ "com.softwaremill.sttp.client4" %% "core" % "4.0.15",
+ "com.softwaremill.sttp.client4" %% "json4s" % "4.0.15",
"org.json4s" %% "json4s-jackson" % "4.0.6"
)
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/PetApi.scala
index 89bc472e65f6..23d94e4e0143 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/PetApi.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/PetApi.scala
@@ -33,11 +33,11 @@ class PetApi(baseUrl: String) {
*
* @param pet Pet object that needs to be added to the store
*/
- def addPet(pet: Pet): Request[Either[ResponseException[String, Exception], Pet]] =
+ def addPet(pet: Pet): Request[Either[ResponseException[String], Pet]] =
basicRequest
.method(Method.POST, uri"$baseUrl/pet")
.contentType("application/json")
- .body(pet)
+ .body(asJson(pet))
.response(asJson[Pet])
/**
@@ -49,11 +49,11 @@ class PetApi(baseUrl: String) {
* @param petId Pet id to delete
* @param apiKey
*/
- def deletePet(petId: Long, apiKey: Option[String] = None): Request[Either[ResponseException[String, Exception], Unit]] =
+ def deletePet(petId: Long, apiKey: Option[String] = None): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.DELETE, uri"$baseUrl/pet/${petId}")
.contentType("application/json")
- .header("api_key", apiKey.toString)
+ .header("api_key", apiKey)
.response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
/**
@@ -65,7 +65,7 @@ class PetApi(baseUrl: String) {
*
* @param status Status values that need to be considered for filter
*/
- def findPetsByStatus(status: Seq[String]): Request[Either[ResponseException[String, Exception], Seq[Pet]]] =
+ def findPetsByStatus(status: Seq[String] = Seq.empty): Request[Either[ResponseException[String], Seq[Pet]]] =
basicRequest
.method(Method.GET, uri"$baseUrl/pet/findByStatus?status=${ status }")
.contentType("application/json")
@@ -80,7 +80,7 @@ class PetApi(baseUrl: String) {
*
* @param tags Tags to filter by
*/
- def findPetsByTags(tags: Seq[String]): Request[Either[ResponseException[String, Exception], Seq[Pet]]] =
+ def findPetsByTags(tags: Seq[String] = Seq.empty): Request[Either[ResponseException[String], Seq[Pet]]] =
basicRequest
.method(Method.GET, uri"$baseUrl/pet/findByTags?tags=${ tags }")
.contentType("application/json")
@@ -99,7 +99,7 @@ class PetApi(baseUrl: String) {
*
* @param petId ID of pet to return
*/
- def getPetById(apiKeyHeader: String)(petId: Long): Request[Either[ResponseException[String, Exception], Pet]] =
+ def getPetById(apiKeyHeader: String)(petId: Long): Request[Either[ResponseException[String], Pet]] =
basicRequest
.method(Method.GET, uri"$baseUrl/pet/${petId}")
.contentType("application/json")
@@ -117,11 +117,11 @@ class PetApi(baseUrl: String) {
*
* @param pet Pet object that needs to be added to the store
*/
- def updatePet(pet: Pet): Request[Either[ResponseException[String, Exception], Pet]] =
+ def updatePet(pet: Pet): Request[Either[ResponseException[String], Pet]] =
basicRequest
.method(Method.PUT, uri"$baseUrl/pet")
.contentType("application/json")
- .body(pet)
+ .body(asJson(pet))
.response(asJson[Pet])
/**
@@ -134,14 +134,14 @@ class PetApi(baseUrl: String) {
* @param name Updated name of the pet
* @param status Updated status of the pet
*/
- def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): Request[Either[ResponseException[String, Exception], Unit]] =
+ def updatePetWithForm(petId: Long, name: Option[String] = None, status: Option[String] = None): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.POST, uri"$baseUrl/pet/${petId}")
.contentType("application/x-www-form-urlencoded")
.body(Map(
"name" -> name,
"status" -> status
- ))
+ ).collect { case (k, Some(v)) => k -> v })
.response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
/**
@@ -154,7 +154,7 @@ class PetApi(baseUrl: String) {
* @param additionalMetadata Additional data to pass to server
* @param file file to upload
*/
- def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): Request[Either[ResponseException[String, Exception], ApiResponse]] =
+ def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): Request[Either[ResponseException[String], ApiResponse]] =
basicRequest
.method(Method.POST, uri"$baseUrl/pet/${petId}/uploadImage")
.contentType("multipart/form-data")
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/StoreApi.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/StoreApi.scala
index 507611de4d13..27efb05281b1 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/StoreApi.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/StoreApi.scala
@@ -31,7 +31,7 @@ class StoreApi(baseUrl: String) {
*
* @param orderId ID of the order that needs to be deleted
*/
- def deleteOrder(orderId: String): Request[Either[ResponseException[String, Exception], Unit]] =
+ def deleteOrder(orderId: String): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.DELETE, uri"$baseUrl/store/order/${orderId}")
.contentType("application/json")
@@ -46,7 +46,7 @@ class StoreApi(baseUrl: String) {
* Available security schemes:
* api_key (apiKey)
*/
- def getInventory(apiKeyHeader: String)(): Request[Either[ResponseException[String, Exception], Map[String, Int]]] =
+ def getInventory(apiKeyHeader: String)(): Request[Either[ResponseException[String], Map[String, Int]]] =
basicRequest
.method(Method.GET, uri"$baseUrl/store/inventory")
.contentType("application/json")
@@ -63,7 +63,7 @@ class StoreApi(baseUrl: String) {
*
* @param orderId ID of pet that needs to be fetched
*/
- def getOrderById(orderId: Long): Request[Either[ResponseException[String, Exception], Order]] =
+ def getOrderById(orderId: Long): Request[Either[ResponseException[String], Order]] =
basicRequest
.method(Method.GET, uri"$baseUrl/store/order/${orderId}")
.contentType("application/json")
@@ -78,11 +78,11 @@ class StoreApi(baseUrl: String) {
*
* @param order order placed for purchasing the pet
*/
- def placeOrder(order: Order): Request[Either[ResponseException[String, Exception], Order]] =
+ def placeOrder(order: Order): Request[Either[ResponseException[String], Order]] =
basicRequest
.method(Method.POST, uri"$baseUrl/store/order")
.contentType("application/json")
- .body(order)
+ .body(asJson(order))
.response(asJson[Order])
}
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/UserApi.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/UserApi.scala
index 67a17958468e..176c21e2b316 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/UserApi.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/api/UserApi.scala
@@ -34,12 +34,12 @@ class UserApi(baseUrl: String) {
*
* @param user Created user object
*/
- def createUser(apiKeyHeader: String)(user: User): Request[Either[ResponseException[String, Exception], Unit]] =
+ def createUser(apiKeyHeader: String)(user: User): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.POST, uri"$baseUrl/user")
.contentType("application/json")
.header("api_key", apiKeyHeader)
- .body(user)
+ .body(asJson(user))
.response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
/**
@@ -53,12 +53,12 @@ class UserApi(baseUrl: String) {
*
* @param user List of user object
*/
- def createUsersWithArrayInput(apiKeyHeader: String)(user: Seq[User]): Request[Either[ResponseException[String, Exception], Unit]] =
+ def createUsersWithArrayInput(apiKeyHeader: String)(user: Seq[User]): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.POST, uri"$baseUrl/user/createWithArray")
.contentType("application/json")
.header("api_key", apiKeyHeader)
- .body(user)
+ .body(asJson(user))
.response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
/**
@@ -72,12 +72,12 @@ class UserApi(baseUrl: String) {
*
* @param user List of user object
*/
- def createUsersWithListInput(apiKeyHeader: String)(user: Seq[User]): Request[Either[ResponseException[String, Exception], Unit]] =
+ def createUsersWithListInput(apiKeyHeader: String)(user: Seq[User]): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.POST, uri"$baseUrl/user/createWithList")
.contentType("application/json")
.header("api_key", apiKeyHeader)
- .body(user)
+ .body(asJson(user))
.response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
/**
@@ -92,7 +92,7 @@ class UserApi(baseUrl: String) {
*
* @param username The name that needs to be deleted
*/
- def deleteUser(apiKeyHeader: String)(username: String): Request[Either[ResponseException[String, Exception], Unit]] =
+ def deleteUser(apiKeyHeader: String)(username: String): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.DELETE, uri"$baseUrl/user/${username}")
.contentType("application/json")
@@ -109,7 +109,7 @@ class UserApi(baseUrl: String) {
*
* @param username The name that needs to be fetched. Use user1 for testing.
*/
- def getUserByName(username: String): Request[Either[ResponseException[String, Exception], User]] =
+ def getUserByName(username: String): Request[Either[ResponseException[String], User]] =
basicRequest
.method(Method.GET, uri"$baseUrl/user/${username}")
.contentType("application/json")
@@ -129,7 +129,7 @@ class UserApi(baseUrl: String) {
* @param username The user name for login
* @param password The password for login in clear text
*/
- def loginUser(username: String, password: String): Request[Either[ResponseException[String, Exception], String]] =
+ def loginUser(username: String, password: String): Request[Either[ResponseException[String], String]] =
basicRequest
.method(Method.GET, uri"$baseUrl/user/login?username=${ username }&password=${ password }")
.contentType("application/json")
@@ -144,7 +144,7 @@ class UserApi(baseUrl: String) {
* Available security schemes:
* api_key (apiKey)
*/
- def logoutUser(apiKeyHeader: String)(): Request[Either[ResponseException[String, Exception], Unit]] =
+ def logoutUser(apiKeyHeader: String)(): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.GET, uri"$baseUrl/user/logout")
.contentType("application/json")
@@ -164,12 +164,12 @@ class UserApi(baseUrl: String) {
* @param username name that need to be deleted
* @param user Updated user object
*/
- def updateUser(apiKeyHeader: String)(username: String, user: User): Request[Either[ResponseException[String, Exception], Unit]] =
+ def updateUser(apiKeyHeader: String)(username: String, user: User): Request[Either[ResponseException[String], Unit]] =
basicRequest
.method(Method.PUT, uri"$baseUrl/user/${username}")
.contentType("application/json")
.header("api_key", apiKeyHeader)
- .body(user)
+ .body(asJson(user))
.response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(()))))
}
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/core/JsonSupport.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/core/JsonSupport.scala
index 7e7f36d126cb..21e57525c69f 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/core/JsonSupport.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/core/JsonSupport.scala
@@ -14,34 +14,11 @@ package org.openapitools.client.core
import org.openapitools.client.model._
import org.json4s._
import sttp.client4.json4s.SttpJson4sApi
-import scala.reflect.ClassTag
object JsonSupport extends SttpJson4sApi {
def enumSerializers: Seq[Serializer[_]] = Seq[Serializer[_]]() :+
- new EnumNameSerializer(OrderEnums.Status) :+
- new EnumNameSerializer(PetEnums.Status)
-
- private class EnumNameSerializer[E <: Enumeration: ClassTag](enumeration: E) extends Serializer[E#Value] {
- import JsonDSL._
- val EnumerationClass: Class[E#Value] = classOf[E#Value]
-
- def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), E#Value] = {
- case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) =>
- json match {
- case JString(value) => enumeration.withName(value)
- case value => throw new MappingException(s"Can't convert $value to $EnumerationClass")
- }
- }
-
- private[this] def isValid(json: JValue) = json match {
- case JString(value) if enumeration.values.exists(_.toString == value) => true
- case _ => false
- }
-
- def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
- case i: E#Value => i.toString
- }
- }
+ OrderEnums.Status.StatusSerializer :+
+ PetEnums.Status.StatusSerializer
implicit val format: Formats = DefaultFormats ++ enumSerializers ++ DateSerializers.all ++ AdditionalTypeSerializers.all
implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/ApiResponse.scala
index b0abb512265e..9d09300eae38 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/ApiResponse.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/ApiResponse.scala
@@ -21,4 +21,3 @@ case class ApiResponse(
`type`: Option[String] = None,
message: Option[String] = None
)
-
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Category.scala
index 169acc49cefd..c0c51c5e53eb 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Category.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Category.scala
@@ -20,4 +20,3 @@ case class Category(
id: Option[Long] = None,
name: Option[String] = None
)
-
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Order.scala
index 9e805e256b21..893cad11d288 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Order.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Order.scala
@@ -26,14 +26,32 @@ case class Order(
status: Option[OrderEnums.Status] = None,
complete: Option[Boolean] = None
)
-
object OrderEnums {
- type Status = Status.Value
- object Status extends Enumeration {
- val Placed = Value("placed")
- val Approved = Value("approved")
- val Delivered = Value("delivered")
- }
+ sealed trait Status
+ object Status {
+ case object Placed extends Status
+ case object Approved extends Status
+ case object Delivered extends Status
+
+ import org.json4s._
+ implicit object StatusSerializer extends Serializer[Status] {
+ def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Status] = {
+ case (TypeInfo(clazz, _), json) if classOf[Status].isAssignableFrom(clazz) =>
+ json match {
+ case JString("placed") => Placed
+ case JString("approved") => Approved
+ case JString("delivered") => Delivered
+ case other => throw new MappingException(s"Invalid Status: $other")
+ }
+ }
+
+ def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
+ case Placed => JString("placed")
+ case Approved => JString("approved")
+ case Delivered => JString("delivered")
+ }
+ }
+ }
}
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Pet.scala
index 3cbab6051284..d5805371fac8 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Pet.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Pet.scala
@@ -25,14 +25,32 @@ case class Pet(
/* pet status in the store */
status: Option[PetEnums.Status] = None
)
-
object PetEnums {
- type Status = Status.Value
- object Status extends Enumeration {
- val Available = Value("available")
- val Pending = Value("pending")
- val Sold = Value("sold")
- }
+ sealed trait Status
+ object Status {
+ case object Available extends Status
+ case object Pending extends Status
+ case object Sold extends Status
+
+ import org.json4s._
+ implicit object StatusSerializer extends Serializer[Status] {
+ def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Status] = {
+ case (TypeInfo(clazz, _), json) if classOf[Status].isAssignableFrom(clazz) =>
+ json match {
+ case JString("available") => Available
+ case JString("pending") => Pending
+ case JString("sold") => Sold
+ case other => throw new MappingException(s"Invalid Status: $other")
+ }
+ }
+
+ def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
+ case Available => JString("available")
+ case Pending => JString("pending")
+ case Sold => JString("sold")
+ }
+ }
+ }
}
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Tag.scala
index c2020246658a..9af834f41b9e 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Tag.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/Tag.scala
@@ -20,4 +20,3 @@ case class Tag(
id: Option[Long] = None,
name: Option[String] = None
)
-
diff --git a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/User.scala
index 6977180bccee..c48dd41458b4 100644
--- a/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/User.scala
+++ b/samples/client/petstore/scala-sttp4/src/main/scala/org/openapitools/client/model/User.scala
@@ -27,4 +27,3 @@ case class User(
/* User Status */
userStatus: Option[Int] = None
)
-