I am generating the Scala code shown below using the openapi-generator.
import examples.openverse.model.{InlineObject, OAuth2RegistrationSuccessful}
import examples.openverse.core.JsonSupport._
import sttp.client3._
import sttp.model.Method
object AuthTokensApi {
def apply(baseUrl: String = "https://api.openverse.engineering/v1") = new AuthTokensApi(baseUrl)
}
def registerApiOauth2(format: String, data: InlineObject
): Request[Either[ResponseException[String, Exception], OAuth2RegistrationSuccessful], Any] =
basicRequest
.method(Method.POST, uri"$baseUrl/auth_tokens/register/?format=${ format }")
.contentType("application/json")
.body(data)
.response(asJson[OAuth2RegistrationSuccessful])
Where InlineObject
and OAuth2RegistrationSuccessful
are simply case classes and JsonSupport
is the following:
object JsonSupport extends SttpJson4sApi {
def enumSerializers: Seq[Serializer[_]] = Seq[Serializer[_]]() :+
new EnumNameSerializer(AudioReportRequestEnums.Reason) :+
new EnumNameSerializer(ImageReportRequestEnums.Reason)
private class EnumNameSerializer[E <: Enumeration: ClassTag](enum: 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) => enum.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 enum.values.exists(_.toString == value) => true
case _ => false
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: E#Value => i.toString
}
}
implicit val format: Formats = DefaultFormats ++ enumSerializers ++ DateSerializers.all
implicit val serialization: org.json4s.Serialization = org.json4s.jackson.Serialization
}
To reproduce I simply call the method registerApiOauth2
with the corresponding parameters.
The problem is that the compiler crashes because of the format
parameter with the following errors:
No implicit view available from examples.openverse.model.InlineObject => sttp.client3.BasicRequestBody.
.body(data)
No org.json4s.Formats found. Try to bring an instance of org.json4s.Formats in scope or use the org.json4s.DefaultFormats.
.response(asJson[OAuth2RegistrationSuccessful])
The problems are resolved when I change format
to any other parameter, such as frmt
. However, this cannot be done because the generated query parameter must be called as such. This is the first time I came across such a problem and was hoping that there is a workaround. My hunch is that the issue stems from the JsonSupport
object.
Link to Scastie with an MCVE: https://scastie.scala-lang.org/oDmYLP8MQOCMqUYEwhJnDg
I managed to reproduce. I added import JsonSupport._
into the class AuthTokensApi
.
With format
https://scastie.scala-lang.org/DmytroMitin/mLtRiWE7SQKySehLJykLAQ doesn't compile.
With frmt
https://scastie.scala-lang.org/DmytroMitin/mLtRiWE7SQKySehLJykLAQ/2 compiles.
The behavior is understandable. The parameter format
of method registerApiOauth2
def registerApiOauth2(format: String, data: InlineObject)...
hides by name the implicit format
defined inside JsonSupport
implicit val format: Formats = DefaultFormats ++ enumSerializers ++ DateSerializers.all
when an implicit is resoled here
def registerApiOauth2(format: String, data: InlineObject)... = {
...
.body(data)(json4sBodySerializer(... /* HERE! */ ..., .....))
...
}
So try to rename either former or latter. If you can't rename any of them then resolve implicits manually and refer the implicit as JsonSupport.format
, not just format
basicRequest
.method(Method.POST, uri"$baseUrl/auth_tokens/register/?format=${format}")
.contentType("application/json")
.body(data)(json4sBodySerializer(JsonSupport.format, serialization))
.response(asJson[OAuth2RegistrationSuccessful](
implicitly[Manifest[OAuth2RegistrationSuccessful]],
JsonSupport.format,
serialization
))
You can read about hiding implicits by name more:
NullPointerException on implicit resolution
Extending an object with a trait which needs implicit member
https://github.com/scala/bug/issues/7788
Simpler example with the same behavior: the code
implicit val i: Int = 1
def m()(implicit x: Int) = ???
def m1()(i: String) = {
m()
}
doesn't compile while
implicit val i1: Int = 1
def m()(implicit x: Int) = ???
def m1()(i: String) = {
m()
}
and
implicit val i: Int = 1
def m()(implicit x: Int) = ???
def m1()(i1: String) = {
m()
}
compile.