Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Aug 24 12:07
    ghostbuster91 synchronize #201
  • Aug 24 12:07

    ghostbuster91 on constraints-14

    Fix validator support in openap… (compare)

  • Aug 24 11:18
    ghostbuster91 synchronize #201
  • Aug 24 11:18

    ghostbuster91 on constraints-14

    Fix previous commit (compare)

  • Aug 24 11:18
    ghostbuster91 synchronize #201
  • Aug 24 11:18

    ghostbuster91 on constraints-14

    Support other kinds of validato… (compare)

  • Aug 24 11:06
    ghostbuster91 synchronize #201
  • Aug 24 11:06

    ghostbuster91 on constraints-14

    Remove implicit validator from … (compare)

  • Aug 23 09:25
    ghostbuster91 synchronize #201
  • Aug 23 09:24

    ghostbuster91 on constraints-14

    Restore formSeqCodecUtf8 back t… Use cohesive syntax when passin… Add proper instances of validat… (compare)

  • Aug 22 20:02
    scala-steward opened #206
  • Aug 22 07:23

    adamw on master

    Update akka-stream to 2.5.25 Merge pull request #204 from sc… (compare)

  • Aug 22 07:23
    adamw closed #204
  • Aug 22 07:22

    adamw on master

    Remove thread local initializer… (compare)

  • Aug 21 23:33
    Voltir opened #205
  • Aug 20 15:49
    scala-steward opened #204
  • Aug 20 11:28
    hejfelix commented #142
  • Aug 20 08:39
    adamw commented #148
  • Aug 20 08:38
    adamw commented #195
  • Aug 20 08:38
    adamw commented #167
Elyran Kogan
@elyrank

is there a way to stream upload file to another server? I have an Akka http route :

path("api" / "v1" / "files" / "Uploaded" / Segment) { fileName =>
        put {
          extractRequestContext {
            context => {
              logger.debug(s"headers: ${context.request.headers}")
              val res = Source.single(context.request)
                .via(Http(system).outgoingConnection(host, port))
                .runWith(Sink.head)
                .flatMap(f => context.complete(f))
            }
       }
}

how can I translate it to a tapir endpoint?

Kasper Kondzielski
@ghostbuster91
@sidnt I found the problem. Tapir fails to derive type class for message since it has optional reference to itself. The minimal code to reproduce is to write:
case class Message(
                    reply_to_message: Option[Message] = None )
I will investigate it further since AFAIK it should work
Kasper Kondzielski
@ghostbuster91
@sidnt I've created an issue for that. Unfortunately it seems to be pretty complicated.
Kasper Kondzielski
@ghostbuster91
@elyrank I see that we will have to update the docs because there is not much information there about streams. Anyway I found some examples which might be useful for you. Here is the endpoint definition which takes stream and returns stream: https://github.com/softwaremill/tapir/blob/bc592bf5883a45d5562ee97bc2bd67609cbf2c1b/tests/src/main/scala/tapir/tests/package.scala#L104 and here is its usage: https://github.com/softwaremill/tapir/blob/cf6d56f82337ed3c7c9697f8eeff5d1c006032d4/server/tests/src/main/scala/tapir/server/tests/ServerTests.scala#L215 Hope it helps
Zach Banducci
@zchbndcc9

Hey y'all, I am implementing validation logic for a few of my routes and I am confused about how this andThenFirstE function is supposed to work with the input for a route. Here is the logic flow that I am trying to implement:

logic = (validateRequest _).andThenFirstE(postCycle _).tupled

the function signature for validateRequest is:

def validateRequest(dto: DTO): F[Either[Error, DTO]]
Could somebody provide me with some guidance on how I might implement something similar?
Kasper Kondzielski
@ghostbuster91
@zchbndcc9 Hi,
it will compose functions when both succeed
it will return error if any of them failed
Is that what you were asking for?
Kasper Kondzielski
@ghostbuster91
I am not sure if I understand the question. Implement something similar to what?
Zach Banducci
@zchbndcc9
Sorry, the question was terribly worded. I am wondering how I would implement a validation feature to perform business validation on a request before passing the request data to the next logic function using andThenFirstE or even a for comprehension. Also I am confused about how U_Tuple and T_Tupleused in andThenFirstE relate to the input and output to the service
Kasper Kondzielski
@ghostbuster91
;) I will try to explain it but please take that with a grain of salt, since I am not an expert
U_Tuple contains all the parameters from the first function, given f: string => int and f andThenFirstE ..., thenU_Tuple == string
So this can be your validation logic which accepts for example string and return valid domain object email
T_Tuple contains all the parameters from the second function, so this can be your domain function which operates on valid input
Kasper Kondzielski
@ghostbuster91
if both functions accept the same amount of elements you just compose them like that (f1 _).andThenFirstE(f2) and if the second one takes more elements (can happen when you validate only one of the inputs) you have to write it that way: (f1 _).andThenFirstE((f2 _).tupled)
Hope that helps, if anything is still unclear please let me know
Zach Banducci
@zchbndcc9
@ghostbuster91 Ok ok things are a little more clear now. Let me play around with it and see if I can get it working
rabzu
@rabzu

Schema for NonEmptyList is showing as

"head": {
      "_1": "string",
      "_2": "string"
    },
    "tail": [
      {
        "_1": "string",
        "_2": "string"
      }
    ]

how can I fix this?

Kasper Kondzielski
@ghostbuster91
@rabzu Here you go implicit def schemaForNel[T](implicit uc: SchemaFor[T]): SchemaFor[NonEmptyList[T]] = SchemaFor(SArray(implicitly[SchemaFor[T]].schema))
Kasper Kondzielski
@ghostbuster91
Basically you need to tell tapir how should it generate schema for nonEmptyList. You are getting this head and tail because by default tapir uses magnolia to generate schemas for case class and nonEmptyList has such properties.
rabzu
@rabzu
oh wow thanks.
Łukasz Sowa
@luksow

Hi! Two quick questions. First - I'd like to provider better errors for consumers of my API (let's say, errors on parsing JSONs). I figured out I need to provide custom DecodeFailureHandler for the EndpointIO.Body[_, _, _] but how can I then access why Circe parser failed? I see that there's failure: DecodeFailure but it's a supertype for couple of case classes and I feel like I'm already really deep down in internals... so probably there's a better way of doing that? Second - I have a server logic (simply - service) that returns:

  sealed trait LoginResponse
  object LoginResponse {
    case class LoggedIn(token: Jwt, at: Instant = Instant.now()) extends LoginResponse
    case object UserNotFound        extends LoginResponse
    case object InvalidCredentials  extends LoginResponse
  }

for LoggedIn I want to return 201 + SomeJSON, for UserNotFound I want to return 404 + ErrorJSON, for InvalidCredentials I want to return 401 + ErrorJson I started like this:

    endpoint
      .post
      .in("auth" / "session")
      .in(jsonBody[LoginRequest])
      .out(oneOf(statusMapping(Created, jsonBody[LoginResponse.LoggedIn])))
      .errorOut(oneOf(statusMapping(Unauthorized, jsonBody[JsonError]), statusMapping(NotFound, jsonBody[JsonError]))) // TODO: ??? :/
      .serverLogic { request =>
        authService.login(request).map {
          case logged @ LoginResponse.LoggedIn(_, _) => Right(logged)
          case LoginResponse.InvalidCredentials => Left(JsonError("Invalid credentials"))
          case LoginResponse.UserNotFound => Left(JsonError("User not found"))
        }
      }

but it won't work because oneOf + statusMapping won't be able to distinguish based on body type. I use single type for representing errors (JsonError) as in rfc7807, so it's not very unusual. Should I tag it somehow in errorOut? Or what's the recommended way of doing this?

Adam Warski
@adamw

@luksow hey, nice to see you here :)

regarding errors - yes, DecodeFailureHandler is the place. It's meant to be customised - in case of json parsing failure using circe you'll get DecodeFailure.Error (with a nested io.circe.Error)

regarding the second question - just use the status code output.
E.g. for errors:
.out(statusCode).out(jsonBody[JsonError])
.serverLogic { r =>
  (...)
  case UserNotFound => Left((StatusCodes.Unauthorized, JsonError("..."))
}
Łukasz Sowa
@luksow
@adamw great, thanks! Back to exploring :)
Adam Warski
@adamw
@luksow if you would have a better idea for the DecodeFailureHandler mechanism (I'm not entirely happy with it either), let me know :)
Łukasz Sowa
@luksow
Well, I haven't thought about it yet, but I kinda like the rejection (exception) handler for Akka HTTP, so I basically have to define a gigantic match-case-like structure for every problem my API can encounter - it's very explicit; don't know if it's feasible for tapir though
I want to package every error into my JsonError structure anyway, so I'll have to figure out a way to do it in tapir :)
Adam Warski
@adamw
@luksow one problem with akka's excepiton handlers is that I was never sure which ones are used in the end. But maybe there's some middle-ground - making DecodeFailureHandlers more compositional, that is, composable from smaller blocks. You would still have to define the composite one in the end, but it could be done through multiple smaller handlers
@luksow maybe create an issue for the on github, if others have ideas as well? :)
Łukasz Sowa
@luksow
Sure, I'll just try to code what I want by hand, with what's there already, and then I'll see how it can be improved
Łukasz Sowa
@luksow

@adamw regarding your solution with statusCodes...

    endpoint
      .post
      .in("auth" / "session")
      .in(jsonBody[LoginRequest])
      .out(statusCode)
      .out(jsonBody[LoginResponse.LoggedIn])
      .errorOut(statusCode)
      .errorOut(jsonBody[JsonError])
      .serverLogic { request =>
        authService.login(request).map {
          case logged @ LoginResponse.LoggedIn(_, _) => Right((Created, logged))
          case LoginResponse.InvalidCredentials => Left((Unauthorized, JsonError("Invalid credentials")))
          case LoginResponse.UserNotFound => Left((NotFound, JsonError("User not found")))
        }
      }

doing it this way (you recommended), I lose all the information of actual status codes (in Swagger etc.), right?

Łukasz Sowa
@luksow

I came up with this lame & crude workaround:

  case class Workaround[T](f: StatusCode, s: T)
  object Workaround {
    implicit def workaroundEncoder[T](implicit encoder: Encoder[T]): Encoder[Workaround[T]] = (a: Workaround[T]) => encoder(a.s)
    implicit def workaroundDecoder[T](implicit decoder: Decoder[T]): Decoder[Workaround[T]] = (c: HCursor) => decoder(c).right.map(Workaround(0, _))
  }

  val newSession =
    endpoint
      .post
      .in("auth" / "session")
      .in(jsonBody[LoginRequest])
      .out(oneOf(statusMapping(Created, jsonBody[LoginResponse.LoggedIn])))
      .errorOut(oneOf(statusMapping(Unauthorized, jsonBody[Workaround[JsonError]]), statusMapping(NotFound, jsonBody[Workaround[JsonError]])))
      .serverLogic { request =>
        authService.login(request).map {
          case logged @ LoginResponse.LoggedIn(_, _) => Right(logged)
          case LoginResponse.InvalidCredentials => Left(Workaround(Unauthorized, JsonError("Invalid credentials")))
          case LoginResponse.UserNotFound => Left(Workaround(NotFound, JsonError("User not found")))
        }
      }

(it's only PoC level)

Jisoo Park
@guersam
Does it return 404 from the server as you expected? I'm worried about the type erasure in the runtime.
Because server side oneOf interpretation depends on runtime reflection AFAIK
Łukasz Sowa
@luksow
oh, right :/
Jisoo Park
@guersam
Currently I have a large sealed abstract class ApiError which has its own JSON codec, and just map relavant domain errors into one of them
And describe error outputs like .errorOut(oneOf(statusMapping(Unauthorized, jsonBody[ApiError.Unauthorized]), statusMapping(NotFound, jsonBody[ApiError.UserNotFound])))
Each error output has its own code and (possibly varying) message like case class Unauthorized(message: String) extends ApiError(1002, message)
Jisoo Park
@guersam
It works in my usecase for now, but still have a room for improvement
Especially something like this :point_up: August 1, 2019 10:08 PM
Jisoo Park
@guersam
I still think error output should be a special case of output which has coproduct-like composition semantic, but couldn't manage to make a PoC implementation yet.
Adam Warski
@adamw
@luksow ah, so you have the same json output, you'd just like to have the status codes enumerated in the docs (even though they all map to the same result)?
maybe a status code output with an enum constraint would work here (we're working on constraints right now)
Adam Warski
@adamw
@guersam btw the statusMapping(...) are also values, which you can pass as a method parameter to a base endpoint, and concatenating the "special cases" with the "default cases" for each endpoint. Wouldn't that work as well instead of adding coproducts (which are complicated, because of discriminators)
Łukasz Sowa
@luksow
@adamw yes, exactly
Adam Warski
@adamw
@luksow can you add your use-case to softwaremill/tapir#48 ?
Łukasz Sowa
@luksow
@adamw sure, in a moment
Jisoo Park
@guersam
@adamw That makes sense, I'll try it out.
Thanks!