Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Status code mappings as part of the endpoint description #55

Closed
adamw opened this issue Mar 16, 2019 · 1 comment
Closed

Status code mappings as part of the endpoint description #55

adamw opened this issue Mar 16, 2019 · 1 comment

Comments

@adamw
Copy link
Member

adamw commented Mar 16, 2019

As suggested on gitter, it would be nice if there was a way to describe status code mappings. Currently, this is only available when interpreting as a server by providing implicit StatusMapper values for error and normal outputs; the openapi documentation always generates two responses: default (for errors) and 200 (for normal outputs). This can only be customised by hand after generating the openapi model.

It would be better if there was a way to capture status code mappings as part of the endpoint description.

Possible API, in Tapir.scala:

  def statusFrom[I](io: EndpointIO[I], default: StatusCode, when: (When[I], StatusCode)*): EndpointIO[I] = ???

  def whenClass[U: ClassTag: SchemaFor]: When[Any] = WhenClass(implicitly[ClassTag[U]], implicitly[SchemaFor[U]])
  def whenValue[U](p: U => Boolean): When[U] = WhenValue(p)

  trait When[-I] {
    def matches(i: I): Boolean
  }
  case class WhenClass[T](ct: ClassTag[T], s: SchemaFor[T]) extends When[Any] {
    override def matches(i: Any): Boolean = ct.runtimeClass.isInstance(i)
  }
  case class WhenValue[T](p: T => Boolean) extends When[T] {
    override def matches(i: T): Boolean = p(i)
  }

usage:

  import tapir._
  import tapir.json.circe._
  import io.circe.generic.auto._

  trait ErrorInfo {
    def z: Boolean
  }
  case class E1(x: String, z: Boolean) extends ErrorInfo
  case class E2(y: Int, z: Boolean) extends ErrorInfo

  implicit val c: CodecForOptional[ErrorInfo, MediaType.Json, _] = null

  val ee = endpoint.out(
    statusFrom(
      jsonBody[ErrorInfo],
      StatusCodes.InternalServerError,
      whenClass[E1] -> StatusCodes.NotFound,
      whenClass[E2] -> StatusCodes.BadRequest,
      whenValue[ErrorInfo](_.z) -> StatusCodes.AlreadyReported
    ))

The server would then be able to generate a proper response code basing on the output value. The openapi docs could contain different output body schemas for different status codes. The client interpreter here wouldn't do anything with this kind of metadata, as the only thing it could do is validate that the status code matches the value received.

Outstanding problems:

  1. for automatic codec derivation this relies on Support for sealed trait families #10: node the null codec in the example above to make the code compile. If there are multiple status codes, this usually means multiple subclasses of a class representing errors.
  2. should statusFrom wrap any kind of input, or only bodies? I think it needs to be constrained to wrapping bodies only, so that we could require the schema of the subclasses, and use these schemas in documentation. This would mean we wouldn't support differentiating status codes on headers (which is the other output option)
@adamw adamw self-assigned this Mar 16, 2019
adamw added a commit that referenced this issue Mar 18, 2019
adamw added a commit that referenced this issue Mar 18, 2019
adamw added a commit that referenced this issue Mar 18, 2019
adamw added a commit that referenced this issue Mar 18, 2019
@adamw
Copy link
Member Author

adamw commented Mar 18, 2019

Implemented as described above. statusFrom can wrap any type of bodies.

@adamw adamw closed this as completed Mar 18, 2019
adamw added a commit that referenced this issue Mar 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant