Tapir не может декодировать список запечатанных трейтов с помощью `DecodingFailure(CNil, List(DownArray))`

В документации Tapir указано, что он поддерживает декодирование запечатанных признаков: https://tapir.softwaremill.com/en/latest/endpoint/customtypes.html#sealed-traits-coproducts .

Однако, когда я пытаюсь сделать это с помощью этого кода, я получаю следующую ошибку:

      import io.circe.generic.auto._
import sttp.client3._
import sttp.tapir.{Schema, _}
import sttp.tapir.client.sttp._
import sttp.tapir.generic.auto._
import sttp.tapir.json.circe._


object TmpApp extends App {

  sealed trait Result {
    def status: String
  }
  final case class IpInfo(
                           query: String,
                           country: String,
                           regionName: String,
                           city: String,
                           lat: Float,
                           lon: Float,
                           isp: String,
                           org: String,
                           as: String,
                           asname: String
                         ) extends Result {
    def status: String = "success"
  }
  final case class Fail(message: String, query: String) extends Result {
    def status: String = "fail"
  }

  val sIpInfo = Schema.derive[IpInfo]
  val sFail = Schema.derive[Fail]
  implicit val sResult: Schema[Result] =
    Schema.oneOfUsingField[Result, String](_.status, _.toString)("success" -> sIpInfo, "fail" -> sFail)

  val apiEndpoint = endpoint.get
    .in("batch")
    .in(query[String]("fields"))
    .in(jsonBody[List[String]])
    .out(jsonBody[List[Result]])
    .errorOut(stringBody)

  val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()

  apiEndpoint
    .toSttpRequestUnsafe(uri"http://ip-api.com/")
    .apply(("4255449", List(
      "127.0.0.1"
    )))
    .send(backend)
    .body
}
      Exception in thread "main" java.lang.IllegalArgumentException: Cannot decode from [{"status":"fail","message":"reserved range","query":"127.0.0.1"}] of request GET http://ip-api.com//batch?fields=4255449
    at sttp.tapir.client.sttp.EndpointToSttpClient.$anonfun$toSttpRequest$7(EndpointToSttpClient.scala:42)
    at sttp.client3.ResponseAs.$anonfun$map$1(ResponseAs.scala:27)
    at sttp.client3.MappedResponseAs.$anonfun$mapWithMetadata$1(ResponseAs.scala:89)
    at sttp.client3.MappedResponseAs.$anonfun$mapWithMetadata$1(ResponseAs.scala:89)
    at sttp.client3.internal.BodyFromResponseAs.$anonfun$doApply$2(BodyFromResponseAs.scala:23)
    at sttp.client3.monad.IdMonad$.map(IdMonad.scala:8)
    at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:42)
    at sttp.client3.internal.BodyFromResponseAs.doApply(BodyFromResponseAs.scala:23)
    at sttp.client3.internal.BodyFromResponseAs.$anonfun$apply$1(BodyFromResponseAs.scala:13)
    at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:42)
    at sttp.client3.internal.BodyFromResponseAs.apply(BodyFromResponseAs.scala:13)
    at sttp.client3.HttpURLConnectionBackend.readResponse(HttpURLConnectionBackend.scala:243)
    at sttp.client3.HttpURLConnectionBackend.$anonfun$send$1(HttpURLConnectionBackend.scala:57)
    at scala.util.Try$.apply(Try.scala:210)
    at sttp.monad.MonadError.handleError(MonadError.scala:14)
    at sttp.monad.MonadError.handleError$(MonadError.scala:13)
    at sttp.client3.monad.IdMonad$.handleError(IdMonad.scala:6)
    at sttp.client3.SttpClientException$.adjustExceptions(SttpClientException.scala:56)
    at sttp.client3.HttpURLConnectionBackend.adjustExceptions(HttpURLConnectionBackend.scala:293)
    at sttp.client3.HttpURLConnectionBackend.send(HttpURLConnectionBackend.scala:31)
    at sttp.client3.HttpURLConnectionBackend.send(HttpURLConnectionBackend.scala:23)
    at sttp.client3.FollowRedirectsBackend.sendWithCounter(FollowRedirectsBackend.scala:22)
    at sttp.client3.FollowRedirectsBackend.send(FollowRedirectsBackend.scala:17)
    at sttp.client3.RequestT.send(RequestT.scala:299)
    at onlinenslookup.ipapi.TmpApp$.delayedEndpoint$onlinenslookup$ipapi$TmpApp$1(TmpApp.scala:53)
    at onlinenslookup.ipapi.TmpApp$delayedInit$body.apply(TmpApp.scala:11)
    at scala.Function0.apply$mcV$sp(Function0.scala:39)
    at scala.Function0.apply$mcV$sp$(Function0.scala:39)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
    at scala.App.$anonfun$main$1(App.scala:73)
    at scala.App.$anonfun$main$1$adapted(App.scala:73)
    at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:553)
    at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:551)
    at scala.collection.AbstractIterable.foreach(Iterable.scala:920)
    at scala.App.main(App.scala:73)
    at scala.App.main$(App.scala:71)
    at onlinenslookup.ipapi.TmpApp$.main(TmpApp.scala:11)
    at onlinenslookup.ipapi.TmpApp.main(TmpApp.scala)
Caused by: DecodingFailure(CNil, List(DownArray))

Process finished with exit code 1

build.sbt:

        "com.softwaremill.sttp.tapir" %% "tapir-core" % "0.17.0-M10",
  "com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "0.17.0-M10",
  "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "0.17.0-M10",

Документацию для этой конкретной конечной точки можно найти здесь: https://ip-api.com/docs/api:batch .

2 ответа

Расшифровка делегирована Цирцее. То, что описано в документации, является лишь выводом Schemas - которые необходимы для документации.

Следовательно, я бы искал причину ошибки, проверяя, есть ли у вас надлежащее Decoderв области видимости и проверки того, что произойдет, если вы попытаетесь декодировать значение примера напрямую, используя circe.

Для дальнейшего использования, вот как я решил проблему.

Оказалось, что мне не хватало Цирцеи Decoder:

      implicit val decoderResult: Decoder[Result] = Decoder[Fail].widen or Decoder[IpInfo].widen

Я также немного подчистил код после того, как заставил его работать.

      import cats.syntax.functor._
import io.circe.Decoder
import io.circe.generic.auto._
import sttp.client3._
import sttp.tapir._
import sttp.tapir.client.sttp._
import sttp.tapir.generic.auto._
import sttp.tapir.json.circe._

object TmpApp extends App {

  sealed trait Result
  final case class IpInfo(
      query: String,
      country: String,
      regionName: String,
      city: String,
      lat: Float,
      lon: Float,
      isp: String,
      org: String,
      as: String,
      asname: String
  )                                                     extends Result
  final case class Fail(message: String, query: String) extends Result

  implicit val decoderResult: Decoder[Result] = Decoder[Fail].widen or Decoder[IpInfo].widen

  val apiEndpoint =
    endpoint.get
      .in("batch")
      .in(query[String]("fields"))
      .in(jsonBody[List[String]])
      .out(jsonBody[List[Result]])
      .errorOut(stringBody)

  val backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend()

  println(
    apiEndpoint
      .toSttpRequestUnsafe(uri"http://ip-api.com/")
      .apply(("4255449", List("127.0.0.1")))
      .send(backend)
      .body
  )
}
Другие вопросы по тегам