Is there a workaround for this format parameter in Scala?
Asked Answered
T

1

1

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

Torque answered 14/9, 2022 at 6:26 Comment(4)
Please prepare MCVE (what should we do step by step to reproduce the behavior you observe i.e. the compile error). Please provide your imports and library dependencies. A parameter name making impact is weird.Numen
Thanks for updating your question but this is still not MCVE! I still can't reproduce the behavior you describe. What are your library dependencies (with versions)? I tried to add "com.softwaremill.sttp.client3" %% "json4s" % "3.8.0", "org.json4s" %% "json4s-jackson" % "4.0.5". Also I added imports org.json4s._, org.json4s.jackson.JsonMethods._, scala.reflect.ClassTag and commdented out some unresolved places: scastie.scala-lang.org/DmytroMitin/SKP8oIh0TPizhpzsv7ATOg/15 I can't see difference in behavior with format vs. frmt.Numen
Maybe you can demonstrate the behavior at Scastie? (See "Build Settings" there too.)Numen
I just added a Scastie link. The only issue is that the error persists even when I change the format variable. I cannot understand why because on my editor the build passes without any issue with the variable set anything but format.Torque
N
1

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.

Numen answered 15/9, 2022 at 10:28 Comment(3)
Thanks for this. Do you think there is a simpler way of solving it just by editing the imports of the AuthTokensApi?Torque
@Torque Hmm, not sure I understand why you think that imports are relevant. Relevant is the collision of names for parameter and implicit val.Numen
@Torque For example for "simpler example" at the end of my answer (implicit val i...) how can imports fix that?Numen

© 2022 - 2024 — McMap. All rights reserved.