Giter VIP home page Giter VIP logo

Comments (5)

adamw avatar adamw commented on June 12, 2024

This happens because you are defining custom codecs from String to your type (Option[List[CarName]] in your case, but from tapir's point of view this is just a type T):

  implicit val carNameCodec: Codec[String, Option[List[CarName]], TextPlain] =
    Codec.string.mapDecode(h => decode[CarName](h, CarName))(encode[CarName](_, _.name))

So we have a String <-> T conversion, where the base type is required (a String). If you then create a query[T] input, tapir it's trying to construct a List[String] <-> T codec, and to do that, it's using the following chain: List[String] -> get the only element (or fail) -> T.

I think defining your codec as String <-> List[CarName], and then creating the input as an optional one: query[Option[List[CarName]]] should work.

from tapir.

adamw avatar adamw commented on June 12, 2024

This seems to work:

package sttp.tapir.examples

import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
import org.apache.pekko.http.scaladsl.server.Route
import sttp.tapir.*
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter

import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}
import scala.io.StdIn

object HelloWorldPekkoServer extends App {
  implicit val actorSystem: ActorSystem = ActorSystem()
  import actorSystem.dispatcher

  case class CarName(name: String)

  def decode[T](s: String, fromString: String => T): DecodeResult[List[T]] = DecodeResult.Value(s.split(",", -1).toList.map(fromString))
  def encode[T](list: List[T], asString: T => String): String = list.map(asString).mkString(",")

  implicit val carNameCodec: Codec[String, List[CarName], TextPlain] =
    Codec.string.mapDecode(h => decode[CarName](h, CarName))(encode[CarName](_, _.name))

  val helloWorld: PublicEndpoint[Option[List[CarName]], Unit, String, Any] =
    endpoint.get.in("hello").in(query[Option[List[CarName]]]("name")).out(stringBody)

  val helloWorldRoute: Route =
    PekkoHttpServerInterpreter().toRoute(helloWorld.serverLogicSuccess(cars => Future.successful(s"Got: $cars!")))

  Await.result(Http().newServerAt("localhost", 8080).bindFlow(helloWorldRoute), 1.minute)

  StdIn.readLine()
}

Test:

[~]% curl "http://localhost:8080/hello?name=x"
Got: Some(List(CarName(x)))!%
[~]% curl "http://localhost:8080/hello?name=x,y"
Got: Some(List(CarName(x), CarName(y)))!%
[~]% curl "http://localhost:8080/hello"
Got: None!%

from tapir.

pk1982r avatar pk1982r commented on June 12, 2024

First - thank you for such a fast answer.
Second - works like a charm.
Third - I am aware of type erasure in JVM, but not understand how removing Option from a codec helps to make a parameter optional. It's counterintuitive. Still, I can live with that as long as it works.
Best regards,
PK

from tapir.

adamw avatar adamw commented on June 12, 2024

This doesn't reach type erasure, everything is done at compiler level (where we have full type info). There are no run-time checks if something is an instance of an option:

For a query[T] input we need a Codec[List[String], T] (the parameter might appear multiple times in the url). So the compiler tries to find the appropriate instance in the implicit scope.

In your case, if you have query[Option[List[CarName]]], the compiler will try to find Codec[List[String], Option[List[CarName]]]. So it tries to look that up - it will try several available options, among them this one, which is a rule on how to construct a Codec[List[T], Option[U]] given a Codec[T, U]. Substituting our types for T and U, we reduce the problem to looking up Codec[String, List[CarName]] - which you provide in your own code base. Problem solved!

Now in your original example, you provided a Codec[String, Option[List[CarName]]. So this can only be used, if we have the String to decode (tapir can't, and doesn't inspect the exact shape of the right-hand side - it can be whatever; here it's an option, but that's completely opaque to tapir). While constructing the Codec[List[String], Option[List[CarName]]] required for the query paramter, another rule was used, to get the single element from the list, and if it's not there - report a missing element.

from tapir.

pk1982r avatar pk1982r commented on June 12, 2024

Thank you very much for such a detailed and clear response. Now I understand the Option should be only in endpoint input declaration (and why input is List not single String)!

from tapir.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.