Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • May 14 23:41
    scala-steward opened #1241
  • May 14 23:41
    scala-steward opened #1240
  • May 14 19:25
    mbore edited #1239
  • May 14 19:25
    mbore opened #1239
  • May 14 19:24

    mbore on adapt-schema-derivation

    modify schema macro for Scala3 (compare)

  • May 14 19:08

    mergify[bot] on master

    Update upickle to 1.3.13 Merge pull request #1236 from s… (compare)

  • May 14 19:08
    mergify[bot] closed #1236
  • May 14 19:08

    mergify[bot] on master

    Update shared:akka, shared:core… Merge pull request #1237 from s… (compare)

  • May 14 19:08
    mergify[bot] closed #1237
  • May 14 18:39
    scala-steward opened #1238
  • May 14 18:39
    scala-steward opened #1237
  • May 14 18:39
    scala-steward opened #1236
  • May 14 15:50
    kubinio123 synchronize #1226
  • May 14 15:49

    kubinio123 on tapir-aws

    adjustments and docs (compare)

  • May 14 14:28

    mergify[bot] on master

    Update refined to 0.9.25 Merge pull request #1235 from s… (compare)

  • May 14 14:28
    mergify[bot] closed #1235
  • May 14 13:51
    scala-steward opened #1235
  • May 14 11:17
    scala-steward opened #1234
  • May 14 10:11
    kubinio123 synchronize #1226
  • May 14 10:11

    kubinio123 on tapir-aws

    runnable example (compare)

Liara
@kiritsuku

Hello, is it possible to upload (stream) files of any size with arbitrary content type? The example on the website (https://tapir.softwaremill.com/en/latest/endpoint/streaming.html) doesn't work for me:

endpoint.out(streamBody(AkkaStreams)(Schema(Schema.derived[List[Person]].schemaType), CodecFormat.Json()))

In play2, there is the HttpEntity.Chunked type:

      def contentType = file.contentType.getOrElse("application/octet-stream")
      def chunks = GridFSStreams(gfs).source(file).map(HttpChunk.Chunk(_))

      Result(
        header = ResponseHeader(OK),
        body = HttpEntity.Chunked(chunks, Some(contentType))
      ).as(contentType)

But I can not replicate the functionality on tapir side since streamBody requires a fixed codec format. Can I somehow avoid specifying the codec format or maybe at least specify a list of codec types?

13 replies
Gabriel Asman
@Qthunder
MatchType derivation seems to not work for Sets ? Rather confusing as it seems like it should be based on Magnolia examples and stuff? Tried to dive into the Magnolia code to understand where it's supposed to be derived (or where the List equivalent is) but it was too complicated for me. Anyone encountered this/have a work around (other than just switching to list, which I might end up doing)
  final case class X(a: Set[String])
  val w                                        = MatchType.gen[X]
2 replies
magnolia: could not find MatchType.Typeclass for type Set[String]
    in parameter 'a' of product type TapirRoutes.this.X

  val w                                        = MatchType.gen[X]
Gabriel Asman
@Qthunder
Another one from me - is there any way documentation for built for stream (input) bodies?
This is what I have right now to set up the streaming endpoint (using fs2-circe), I think it "works" but the documentation produced is not very useful. Is there a way to incorporate the actual schema of MyType in the documentation too?
      .in(streamBody(Fs2Streams[IO])(Schema.binary, CodecFormat.Json(), StandardCharsets.UTF_8.some))
      .serverLogicForCurrent(
        _.through(byteStreamParser)
          .through(decoder[IO, MyType])
          .asRight
          .pure[IO]
      )
2 replies
Liara
@kiritsuku
In play, there exists RemoteConnection, as part as the RequestHeader class. Is there a comparable way to get access to the raw request in tapir? I would need the IP address and the protocol (http, https) of the request for example.
2 replies
Blaž Marinović
@bmarinovic
hi, is it possible to render Cats' NonEmptyList[T] as regular List[T] in OpenApi generator? Currently, for Nel[Int], I'm getting it as:
'#/components/schemas/NonEmptyList_Int'

    NonEmptyList_Int:
      required:
      - head
      type: object
      properties:
        head:
          type: integer
        tail:
          type: array
          items:
            type: integer
1 reply
ybasket
@ybasket:matrix.org
[m]
@bmarinovic: The following snippet should do that (have no good setup for testing right now, but works fine with very similar code for NonEmptyMap):
  implicit def schemaForNonEmptyList[V](implicit s: Schema[List[V]]): Schema[NonEmptyList[V]] =
    s.map(NonEmptyList.fromList)(_.toList)
Łukasz Biały
@lbialy
hi, I'm trying to add tracing and metrics to an akka-http based server app that uses tapir. is there any established way to do this? I've tried wrapping resulting Route but that doesn't really work if the route doesn't match (timers start before route is evaluated). I have therefore moved my monitoring code to wrap the function passed to serverLogic method and this provides useful metrics but I can't gather data such as the resulting http code or the actual http path from hostname. Any ideas?
8 replies
Blaž Marinović
@bmarinovic
@ybasket:matrix.org thanks, just what I needed! it works :)
Anders Schwartz
@adschwartz
Hi. Is there a way to turn off the "try it out” feature that the Swagger UI launches with through tapir? I’m serving it on http4s.
Looks like the Swagger UI itself supports this feature but I wasn’t able to find a way to configure that trough tapir: https://github.com/swagger-api/swagger-ui/issues/3725#issuecomment-654284336
1 reply
Adam Warski
@adamw

A proposal to add endpoint / request interceptors and more flexible server interpreter callbacks: softwaremill/tapir#1065

Opinions and suggestions welcome - thanks! :)

Pascal Danek
@pascaldanek
Hello, I have an email case class in my application: case class EmailAddress(value: String). I use it in my endpoint as a simple string. While I was able to do it using custom tapir codecs, i cannot get the right schema in the documentation. For now I just did val emailSchema = Schema.string but in the swagger I stil see "email": {"value": "email"}. Any idea ?
2 replies
perok
@perok:matrix.org
[m]
@pascaldanek: Use case class EmailAddress(value: String) extends AnyVal https://scastie.scala-lang.org/IncOWYTPS22Mb8srSeF75Q
1 reply
julbarg
@julbarg
Hi everyone what is the best way to use ADTs on Tapir to define enums?
1 reply
Marek Kadek
@KadekM
in(jsonBody[Option[Json]) fails on no body present with Invalid value for: body (exhausted input), is there way to achieve it ?
3 replies
Michał Kobzik
@mkobzik
Hi, is there any way to transform ServerEndpoint[Unit, Nothing, Unit, Fs2Streams[F] with WebSockets, F] into ServerEndpoint[Unit, Nothing, Unit, Fs2Streams[G] with WebSockets, G]? I've tried using imapK(fToG)(gToF), but it changes only effect type, leaving Fs2Streams untouched ServerEndpoint[Unit, Nothing, Unit, Fs2Streams[F] with WebSockets, G]
1 reply
Jonathan Neufeld
@jonathan-neufeld-asurion
Hi, I am new to both Tapir and Gitter, is this the right place to ask questions and is there a good set of working examples I can use to better-understand how the declarative syntax works?
1 reply
Jens Grassel
@jan0sch
Hi all, if we have a list of endpoints, would it be possible for the compiler to issue warning if some of them trigger on the same path and method?
Theoretically we could parse them and check it ourselfes but having this automatically would be awesome. Especially if projects grow over time.
2 replies
johnhungerford
@johnhungerford

I have found that adding .validate(Validator.Min(0, true)) to a field when defining a schema does not propagate to the openapi spec that I generate. Example:

// Code
case class TestClass( @validate( Validator.Min(0, false ) ) intVal : Int )

val myEndpoint = endpoint.get.in( jsonBody[ TestClass ] )

// Resulting open api spec
   TestClass:
      required:
      - intVal
      type: object
      properties:
        intVal:
          type: integer

Is this expected?

I get the same result when I define a schema manually
johnhungerford
@johnhungerford
Looks like my issue is addressed here: softwaremill/tapir#1051
3 replies
Dmitriy Kovalenko
@dkovalenko
Hi all. Is there any way to map input from query params into case class with more than 22 fields?
1 reply
Maybe some kind of intermediate map/list from tuple? I don't want to split my domain class to 2-3 classes just to make tapir work.
Jonathan Neufeld
@jonathan-neufeld-asurion
Hello, how does one express documentation for specific status return codes that have no body? I made a helper like this: EndpointIO.Empty(Codec.idPlain(), EndpointIO.Info.empty).map(_ => obj)(_ => ()).description("my description") but it doesn't work and isn't documented.
1 reply
heksesang
@heksenlied:matrix.org
[m]
Is it possible to define multiple CodecFormat for the output of an endpoint, in the case that it might return one of multiple content-types depending on some query parameters or headers?
1 reply
Gilad Hoch
@hochgi

Hi,
I'm using tapir, and really liking it so far. But there's something I find a bit odd in the default behaviour of endpoint input, but maybe I'm not using it correctly?
Anyway, the usecase is simple. consider some simple endpoint like:

val getUsers: Endpoint[Int, String, String, Any] = {
  val userId = path[Int].description("The user ID is a 32-bit signed integer")
  endpoint
    .name("get user")
    .get
    .in("api" / "users" / userId)
    .errorOut(stringBody)
    .out(stringBody)
}

I would expect that a call like: GET <host>/api/users/foo would result with something like:
400 BadRequest : 'foo' is not a valid Int
instead we get:
500 Internal Server Error : There was an internal server error.

Looking at the code, I see that path has a context bound for a codec, which I guess is always this:

implicit val int: Codec[String, Int, TextPlain] = stringCodec[Int](_.toInt).schema(Schema.schemaForInt)

and in the implementation, it's just string codec mapped with _.toInt, leaving the validator as Validator.pass.

So this seems a bit weird to me, since clearly what we want in such a case is to validate the primitives properly, thus returning 400, and not 500 in case of invalid input.

  1. Is there anything I missed? Is there an easy fix?
  2. If not, would you like me to open an issue for that?

Thanks!

13 replies
Doug Clinton
@DougC
I have a case class for which I have a custom circe encoder that adds an extra field to the generated json. How can I modify the schema to add a description of the extra field?
5 replies
Thijs Broersen
@ThijsBroersen
I upgraded Tapir from 0.17.9 to 0.17.19 and now RedocHttp4s is broken. I get a 308 when I want to view the rendered docs. Could not find any related (bug-)ticket in github. I see some changes in the source-code but cannot find any migration instructions. Anyone got the same issue?
Thijs Broersen
@ThijsBroersen
turns out contextPath cannot be Nil or an empty String
6 replies
also RedocHttp4s and SwaggerHttp4s have different implementations (e.g. contextPath is a List[String] for the first and String for the latter)
sken
@sken77
would adding a server interpreter with zio-http be considered?
i know we have zio-http4s already and zio-http is not official zio but 3rd party, although is endorsed by zio devs
sken
@sken77
is there a guide to write your own server interpeter. what is the interface?
3 replies
Mark Hammons
@markehammons
how difficult would it be to add custom authentication and one-of authentication to tapir?
is it on the roadmap at all at the moment? I am wanting to adopt tapir for a project, but our auth needs (one-of 3 possible auths must be met, one of the auths is an non-conforming "oauth") block us actually transitioning to tapir
15 replies
Mathieu Prevel
@mprevel
Hi guys,
I am using tapir in a multi modules project (frontend, backend, shared).
If my understanding is good I should define the endpoints in the shared module, the server interpreter in the backend module and the client interpreter in the frontend (or better, the shared module).
My models also use newtype and so my project uses tapir-newtype integration.
Nonetheless there is no release of tapir-newtype for scala.js
Is there a limitation about publishing this artifact for scala.js ?
5 replies
mcallisto
@mcallisto
Hi @mprevel I would like to use tapir on a scala.js frontend as well, is there any example you could point to? After reading https://tapir.softwaremill.com/en/latest/client/sttp.html I have been able to send a request using the FetchBackend. But how to implement endpoints? I have added to my dependencies
"com.softwaremill.sttp.tapir" %%% "tapir-sttp-client" % "0.18.0-M2" but I am struggling to find a way.
mcallisto
@mcallisto
I might be mistaken, but looking at the errors it seems that one of the issues is that part oftapir-sttp-client is relying on methods that don't have a counterpart in scala.js, such as java.time.Instant.atZone(java.time.ZoneId)java.time.ZonedDateTime or java.time.LocalTime.parse(java.lang.CharSequence)java.time.LocalTime. Has anyone experience on Tapir in scala.js?
7 replies
Vitalii
@vitaliihonta
Hi there! Did anybody had a chance to look at my PR with mock-server integration? softwaremill/tapir#1097 I would appreciate someone can join the discussion about testing so that I can go forward
1 reply
Vadim Agishev
@Chicker

Hi there!

Could you answer me, why the type ZServerEndpoint[R, I, E, O] = ServerEndpoint[I, E, O, Any, RIO[R, *]] is coupled to RIO, where error type is Throwable instead of using more flexible ZIO[R, E, *]?

I have a problem, I have an endpoint of ServerEndpoint[(String, Unit), ApiError, String, Any, ZIO[Any, ApiError, *]] type where type parameter E is the my custom type ApiError <: Throwable and I can't use ZHttp4sServerInterpreter because of type mismatch error: ZIO[Any, ApiError, ] can't be used in RIO[Any, ] context.
Thanks in advance!

6 replies
Martin Allaire
@mallaire77
Howdy guys, has .toDirective been removed or migrated as an option from AkkaHttpServerInterpreter in v0.18?
2 replies
vonchav
@voonchav_gitlab

Hi Adam, I'm excited about a new version of Tapir. I upgraded Tapir to 0.18 M4. Minor changes mostly, though I wanted to bring up a few items before opening tickets.

  1. The respond method in sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler, has these two parameters, badRequestOnPathErrorIfPathShapeMatches & badRequestOnPathInvalidIfPathShapeMatches. I don't think their semantics have changed in the new version. By setting both to true, I'm expecting any mismatch in the shape of the URL should result a 400-BadRequest. However, currently those tests that were designed to test this behavior in my test suites are failing because the returning status code is now 404-NotFound. I can see in the code that it seems to return 400-BadRequest (Some(onlyStatus(StatusCode.BadRequest))) but I'd like to confirm with you if this is a bug.

  2. In the new sttp.tapir.server.http4s.Http4sServerOptions, it provides Log.defaultServerLog as the default implementation. My problem with the current impl is that it doesn't allow me to override the logAllDecodeFailures parameter in the DefaultServerLog constructor. I have this flag as an app setting so that I can turn it on for troubleshooting purpose. The current impl of Log.defaultServerLog returns ServerLog[F[Unit]], so I can't call copy to set the logAllDecodeFailures parameter myself. I can submit a PR for this but if you think it's trivial enough that you can squeeze in a fix in the next release, I'd leave it to you then.

  3. Also, for the implementation of customInterceptors in sttp.tapir.server.http4s.Http4sServerOptions, right now the implementation uses ExecutionContext.Implicits.global. I wonder if it makes sense to make it a parameter so it can be passed in from call sites. With the current implementation, in order to provide a different executor, the only choice for me as a user is to re-implement customInterceptors myself.

Please let me know your thoughts on these. Thank you again for all your hard work for this amazing lib. Looking forward to the official release.

18 replies
allawala
@allawala

Hi, I need some help. I am upgrading Tapir from v 0.16.16 to 0.17.19. My http routes are based off this github project https://github.com/saraiva132/zio-cats-backend from the zio website resources section. In short the old routes are using the toRoutesR variant where i was passing the environment in via URIO eg

object ShortenRoutes {
    val routes: URIO[BitlyEnv, HttpRoutes[Task]] =
        ShortenApi.getShortenEndpoint.toRoutesR {
          case (reqCtx, url) =>
            Bitly.shortenUrl(url).mapToClientError(reqCtx)
        }

}

I am trying to convert it to something like

object ShortenRoutes {
    val routes: HttpRoutes[RIO[BitlyEnv, *]] = ZHttp4sServerInterpreter.from(ShortenApi.getShortenEndpoint) { req =>
        val (reqCtx, url) = req
        Bitly.shortenUrl(url).mapToClientError(reqCtx)
      }.toRoutes
}

I am using http4s server where previously i had

  private val appRoutes: URIO[AppEnv, HttpApp[Task]] =
    for {
      healthRoutes  <- HealthRoutes.routes
      shortenRoutes <- ShortenRoutes.routes
      docsRoutes     = new SwaggerHttp4s(yaml).routes[Task]
    } yield {
      (healthRoutes <+> shortenRoutes <+> docsRoutes).orNotFound
    }

and in my BlazeServerBuilder I had

    for {
      app                            <- appRoutes
      config                         <- getConfig[HttpServerConfig]
      implicit0(rts: Runtime[Clock]) <- ZIO.runtime[Clock]
      ec                             <- ZIO.descriptor.map(_.executor.asEC)
      _ <-
        BlazeServerBuilder[Task](ec)
          .bindHttp(config.port, config.host)
          .withConnectorPoolSize(config.poolSize)
          .withIdleTimeout(config.idleTimeout.asScala)
          .withResponseHeaderTimeout(config.responseHeaderTimeout.asScala)
          .withoutBanner
          .withHttpApp(app)
          .serve
          .compile
          .drain
    } yield ()

The new code looks like

....
     BlazeServerBuilder[RIO[BitlyEnv, *]](ec)
....
    .withHttpApp(Router("/" -> (ShortenRoutes.routes <+> HealthRoutes.routes <+> new SwaggerHttp4s(yaml).routes)).orNotFound)

where BitlyEnv contains Clock

The ShortenRoutes takes BitlyEnv as the environment, where as the HealthRoutes only takes Clock since its method signature is HttpRoutes[RIO[Clock, *]].

The compiler complains about type mismatches unless i pass in the BitlyEnv to the HealthRoute as well.

New to zio/http4s and pure functional programming so I am struggling to figure out what I am doing wrong or how to have multiple routes take in different environments and make the compiler happy. Any help would be appreciated. Again , i cant really share all the code, but as mentioned, its based on the github example above specifically the http.routes package

12 replies
Edvin Lundberg
@Edvin-san
Hi, I have a server endpoint defined with tapir. The logic simply calls another service with sttp client. How could I forward the HttpError from the sttp client as the tapir endpoint error?
4 replies
ex0ns
@ex0ns

Hello, I was playing with tapir a bit and I struggle a bit with the type involved in route definition.
I'm currently using this code to create a ZLayer from my services

  // type Routes[T] = ZIO[Clock, Throwable, T]
  // type ZRoutes   = HttpRoutes[Routes]
  def swaggerRoutes: ZRoutes =
    new SwaggerHttp4s(swaggerDefinition, contextPath = "docs").routes[Routes]

  def routes: URLayer[Has[GameService], Has[ZRoutes]] =
    ZLayer.fromService[GameService, ZRoutes] { gameService =>
      val r = ZHttp4sServerInterpreter
        .from(
          List(
            endpoints.startGame.zServerLogic(_ => gameService.startGame()),
          )
        )
        .toRoutes
      r <+> swaggerRoutes
    }

And I wanted to change my code to use method from gameService's companion object, instead of using the ZLayer.fromServices so I ended up doing that

type RoutesTest[T] = ZIO[Has[GameService] with Has[Clock.Service], Throwable, T]
  type FinalRoutes   = HttpRoutes[RoutesTest]
  def routes2: FinalRoutes = {
    val r: HttpRoutes[RoutesTest] = ZHttp4sServerInterpreter
      .from[Has[GameService]](
        List(
          endpoints.startGame.zServerLogic(_ => GameService.startGame())
        )
      )
      .toRoutes
    r <+>
      new SwaggerHttp4s(swaggerDefinition, contextPath = "docs").routes[RoutesTest]
  }

But I don't really like working with those HttpRoutes and Kleisli. Is there a way for me to create a ZIO layer from this ? Or is it not the way it's supposed to work, and having a ZLayer around those HttpRoutes is not recommended

4 replies
Andy Czerwonka
@andyczerwonka

about to take on an upgrade from 0.17.0-M9, so wondering if someone can point me to key changes and/or a migration strategy? Just looking at the docs and comparing it to my imports, looks like some significant breaking changes. E.g, my imports

object MyTapir
    extends Tapir
    with TapirAkkaHttpServer
    with TapirOpenAPICirceYaml
    with TapirAliases
    with ValidatorDerivation
    with SchemaDerivation {

I see the TapirAkkaHttpServer import has changed, I'm curious about ValidatorDerivation and others. I also see that someone contributed Json4s support, so I'll need to bring that in.

2 replies
Andy Czerwonka
@andyczerwonka

Looks like a few significant breaking changes. In reverse order:

  1. https://github.com/softwaremill/tapir/releases/tag/v0.18.0-M1
  2. https://github.com/softwaremill/tapir/releases/tag/v0.17.0
  3. https://github.com/softwaremill/tapir/releases/tag/v0.17.0-M9

If there is anything else I should look out for, please let me know.

1 reply
ex0ns
@ex0ns

Is there a way to combine EndpointInput using or instand of and. My goal would be to support either
path/to/session/XXXXX or a custom header x-custom-session = XXXXX
I've defined those two EndpointInput:

  val sessionHeader: EndpointInput[UUID]    = header[UUID]("session")
  val sessionHeaderUrl: EndpointInput[UUID] = path[UUID]("x-custom-session")

But I don't know how to combine them

2 replies
balthz
@balthz

Hi there! I have a question regarding testing. Often, I find myself in the situation where I don't just have an Endpoint but a ServerEndpoint with a bit of logic that I'd like to test. Example:

def twice(logic: IO[String]): ServerEndpoint[Unit, String, String, Any, IO] =
    endpoint.get
      .in("foo")
      .out(stringBody)
      .errorOut(stringBody) // (*)
      .serverLogic { _ =>
        logic
          .map(s => s"$s$s")
          .attempt
          .map {
            case Left(throwable) => Left(throwable.getMessage)
            case Right(s)        => Right(s)
          }
      }

The SttpBackendStub (from the docs) is super helpful to test just the first part (i.e., up to (*)) and of course, I could test the logic inside .serverLogic separately, by factoring it out into a function.

However, if I want a more end-to-end type test, is there an easy way to do this?

Something like (I'm phantasizing here):

val backend = SttpBackendStub.forServerEndpoint(twice(IO("bar")))
client3.basicRequest
        .get(uri"http://abc.xyz/foo")
        .send(backend)
        .body should equal(Right("""barbar"""))

?

3 replies