Home > Software engineering >  Is there a workaround for this format parameter in Scala?
Is there a workaround for this format parameter in Scala?

Time:09-15

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

CodePudding user response:

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

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.

  • Related