Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a0443cc
feat: add support for `oneOf` in sttp4 client
plaflamme Feb 7, 2026
4437a9e
fix: pass `Option[String]` directly to .header
plaflamme Feb 7, 2026
9b9f05e
fix: use oneOf mapping to map discriminator values to types
plaflamme Feb 9, 2026
bd2a4bd
fix: remove unecessary whitespaces
plaflamme Feb 9, 2026
e594023
fix: generate samples, make a new one for sttp4-circe
plaflamme Feb 9, 2026
7aba995
fix: make partial function total
plaflamme Feb 9, 2026
921be4b
fix: only use semiauto derivation
plaflamme Feb 10, 2026
74a8c3e
fix: missing comma
plaflamme Feb 10, 2026
4da2654
fix: re-run generate-samples script
plaflamme Feb 11, 2026
1e8c387
docs: update documentation
plaflamme Feb 11, 2026
1e3be60
fix: spaces, not tabs
plaflamme Feb 11, 2026
ac70f60
Merge remote-tracking branch 'upstream/master' into sttp4-client-oneof
plaflamme Feb 11, 2026
545de95
ci: add new samples directory
plaflamme Feb 12, 2026
14227c6
fix: bump scala version
plaflamme Feb 12, 2026
8de5683
fix: url-form-encoded cases
plaflamme Feb 12, 2026
ce5ea99
Merge remote-tracking branch 'upstream/master' into sttp4-client-oneof
plaflamme Mar 6, 2026
cabdbe6
chore: regenerate sttp4 samples after upstream merge
plaflamme Mar 6, 2026
d7355ec
fix: sttp4 json4s JsonSupport uses sealed trait serializers instead o…
plaflamme Mar 6, 2026
2aeb207
fix: sttp4 oneOf with parent properties - resolve aliased children, p…
plaflamme Mar 16, 2026
ac1b84e
Merge remote-tracking branch 'upstream/master' into sttp4-client-oneof
plaflamme Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/samples-scala-client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- samples/client/petstore/scala-sttp/**
- samples/client/petstore/scala-sttp-circe/**
- samples/client/petstore/scala-sttp4/**
- samples/client/petstore/scala-sttp4-circe/**
pull_request:
paths:
- samples/client/petstore/scalaz/**
Expand All @@ -21,6 +22,7 @@ on:
- samples/client/petstore/scala-sttp/**
- samples/client/petstore/scala-sttp-circe/**
- samples/client/petstore/scala-sttp4/**
- samples/client/petstore/scala-sttp4-circe/**
jobs:
build:
name: Build sbt/Scala
Expand All @@ -38,6 +40,7 @@ jobs:
- samples/client/petstore/scala-sttp
- samples/client/petstore/scala-sttp-circe
- samples/client/petstore/scala-sttp4
- samples/client/petstore/scala-sttp4-circe
services:
petstore-api:
image: swaggerapi/petstore
Expand Down
7 changes: 7 additions & 0 deletions bin/configs/scala-sttp4-circe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
generatorName: scala-sttp4
outputDir: samples/client/petstore/scala-sttp4-circe
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/scala-sttp4
additionalProperties:
hideGenerationTimestamp: "true"
jsonLibrary: "circe"
4 changes: 3 additions & 1 deletion docs/generators/scala-sttp4.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
| ------ | ----------- | ------ | ------- |
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|apiPackage|package for generated api classes| |null|
|circeExtrasVersion|The version of circe-generic-extras library| |0.14.4|
|circeVersion|The version of circe library| |0.14.15|
|dateLibrary|Option. Date library to use|<dl><dt>**joda**</dt><dd>Joda (for legacy app)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (preferred for JDK 1.8+)</dd></dl>|java8|
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
Expand All @@ -36,7 +38,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |null|
|sttpClientVersion|The version of sttp client| |4.0.0-M1|
|sttpClientVersion|The version of sttp client| |4.0.15|

## IMPORT MAPPING

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class {{classname}}(baseUrl: String) {
{{>javadoc}}

{{/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}}
Expand All @@ -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}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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}}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}}
Original file line number Diff line number Diff line change
@@ -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}}
{{#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}}
Loading
Loading