Skip to content

Commit

Permalink
Adding (and using) safeDecode which handles exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Apr 3, 2019
1 parent 2b1b162 commit bf29b29
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ class EndpointToSttpClient(clientOptions: SttpClientOptions) {
.map {
case EndpointIO.Body(codec, _) =>
val so = if (codec.meta.isOptional && body == "") None else Some(body)
getOrThrow(codec.decode(so))
getOrThrow(codec.safeDecode(so))

case EndpointIO.StreamBodyWrapper(_) =>
body

case EndpointIO.Header(name, codec, _) =>
getOrThrow(codec.decode(meta.headers(name).toList))
getOrThrow(codec.safeDecode(meta.headers(name).toList))

case EndpointIO.Headers(_) =>
meta.headers
Expand Down
21 changes: 18 additions & 3 deletions core/src/main/scala/tapir/Codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import tapir.internal.UrlencodedData
import tapir.model.Part

import scala.annotation.implicitNotFound
import scala.util.{Failure, Success, Try}

/**
* A pair of functions, one to encode a value of type `T` to a raw value of type `R`,
Expand All @@ -30,7 +31,7 @@ Is there an implicit schema for: ${T}, and all of its components?
(codecs are looked up as implicit values of type Codec[${T}, ${M}, _];
schemas are looked up as implicit values of type SchemaFor[${T}])
""")
trait Codec[T, M <: MediaType, R] { outer =>
trait Codec[T, M <: MediaType, R] extends Decode[R, T] { outer =>
def encode(t: T): R
def decode(s: R): DecodeResult[T]
def meta: CodecMeta[M, R]
Expand Down Expand Up @@ -167,7 +168,7 @@ Is there an implicit schema for: ${T}, and all of its components?
(codecs are looked up as implicit values of type Codec[${T}, ${M}, _];
schemas are looked up as implicit values of type SchemaFor[${T}])
""")
trait CodecForOptional[T, M <: MediaType, R] { outer =>
trait CodecForOptional[T, M <: MediaType, R] extends Decode[Option[R], T] { outer =>
def encode(t: T): Option[R]
def decode(s: Option[R]): DecodeResult[T]
def meta: CodecMeta[M, R]
Expand Down Expand Up @@ -219,7 +220,7 @@ Is there an implicit schema for: ${T}, and all of its components?
(codecs are looked up as implicit values of type Codec[${T}, ${M}, _];
schemas are looked up as implicit values of type SchemaFor[${T}])
""")
trait CodecForMany[T, M <: MediaType, R] { outer =>
trait CodecForMany[T, M <: MediaType, R] extends Decode[Seq[R], T] { outer =>
def encode(t: T): Seq[R]
def decode(s: Seq[R]): DecodeResult[T]
def meta: CodecMeta[M, R]
Expand Down Expand Up @@ -306,3 +307,17 @@ case class MultipartValueType(partCodecs: Map[String, AnyCodecForMany], defaultC
private[tapir] def partCodec(name: String): Option[AnyCodecForMany] = partCodecs.get(name).orElse(defaultCodec)
def partCodecMeta(name: String): Option[AnyCodecMeta] = partCodec(name).map(_.meta)
}

trait Decode[F, T] {
def decode(s: F): DecodeResult[T]

/**
* Calls `decode` and catches any exceptions that might occur, converting them to decode failures.
*/
def safeDecode(f: F): DecodeResult[T] = {
Try(decode(f)) match {
case Success(r) => r
case Failure(e) => DecodeResult.Error(f.toString, e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object FormCodecMacros {
case (field, codec) =>
val fieldName = field.name.decodedName.toString
q"""val transformedName = $conf.transformMemberName($fieldName)
$codec.decode(paramsMap.get(transformedName).toList.flatten)"""
$codec.safeDecode(paramsMap.get(transformedName).toList.flatten)"""
}

val codecTree = q"""
Expand Down
16 changes: 4 additions & 12 deletions core/src/main/scala/tapir/internal/server/DecodeInputs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import tapir.{DecodeFailure, DecodeResult, EndpointIO, EndpointInput}
import tapir.internal._

import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}

trait DecodeInputsResult
object DecodeInputsResult {
Expand Down Expand Up @@ -88,7 +87,7 @@ object DecodeInputs {
case (input @ EndpointInput.PathCapture(codec, _, _)) +: inputsTail =>
ctx.nextPathSegment match {
case (Some(s), ctx2) =>
decodeHandleExceptions(codec.decode, s) match {
codec.safeDecode(s) match {
case DecodeResult.Value(v) => apply(inputsTail, values.value(input, v), ctx2)
case failure: DecodeFailure => (DecodeInputsResult.Failure(input, failure), ctx)
}
Expand All @@ -107,7 +106,7 @@ object DecodeInputs {
apply(inputsTail, values.value(input, ps), ctx2)

case (input @ EndpointInput.Query(name, codec, _)) +: inputsTail =>
decodeHandleExceptions(codec.decode, ctx.queryParameter(name).toList) match {
codec.safeDecode(ctx.queryParameter(name).toList) match {
case DecodeResult.Value(v) => apply(inputsTail, values.value(input, v), ctx)
case failure: DecodeFailure => (DecodeInputsResult.Failure(input, failure), ctx)
}
Expand All @@ -118,14 +117,14 @@ object DecodeInputs {
case (input @ EndpointInput.Cookie(name, codec, _)) +: inputsTail =>
val allCookies = DecodeResult.sequence(ctx.headers.filter(_._1 == Cookie.HeaderName).map(p => Cookie.parse(p._2)).toList)
val cookieValue =
allCookies.map(_.flatten.find(_.name == name)).flatMap(cookie => decodeHandleExceptions(codec.decode, cookie.map(_.value)))
allCookies.map(_.flatten.find(_.name == name)).flatMap(cookie => codec.safeDecode(cookie.map(_.value)))
cookieValue match {
case DecodeResult.Value(v) => apply(inputsTail, values.value(input, v), ctx)
case failure: DecodeFailure => (DecodeInputsResult.Failure(input, failure), ctx)
}

case (input @ EndpointIO.Header(name, codec, _)) +: inputsTail =>
decodeHandleExceptions(codec.decode, ctx.header(name)) match {
codec.safeDecode(ctx.header(name)) match {
case DecodeResult.Value(v) => apply(inputsTail, values.value(input, v), ctx)
case failure: DecodeFailure => (DecodeInputsResult.Failure(input, failure), ctx)
}
Expand Down Expand Up @@ -164,11 +163,4 @@ object DecodeInputs {
case None => None
}
}

private def decodeHandleExceptions[F, T](decode: F => DecodeResult[T], f: F): DecodeResult[T] = {
Try(decode(f)) match {
case Success(r) => r
case Failure(e) => DecodeResult.Error(f.toString, e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private[akkahttp] class EndpointToAkkaDirective(serverOptions: AkkaHttpServerOpt
case Some(bodyInput @ EndpointIO.Body(codec, _)) =>
rawBodyDirective(codec.meta.rawValueType)
.map { v =>
codec.decode(Some(v)) match {
codec.safeDecode(Some(v)) match {
case DecodeResult.Value(bodyV) => values.value(bodyInput, bodyV)
case failure: DecodeFailure => DecodeInputsResult.Failure(bodyInput, failure): DecodeInputsResult
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class EndpointToHttp4sServer[F[_]: Sync: ContextShift](serverOptions: Http4sServ
values.bodyInput match {
case Some(bodyInput @ EndpointIO.Body(codec, _)) =>
new Http4sRequestToRawBody(serverOptions).apply(req.body, codec.meta.rawValueType, req.charset, req).map { v =>
codec.decode(Some(v)) match {
codec.safeDecode(Some(v)) match {
case DecodeResult.Value(bodyV) => values.value(bodyInput, bodyV)
case failure: DecodeFailure => DecodeInputsResult.Failure(bodyInput, failure): DecodeInputsResult
}
Expand Down

0 comments on commit bf29b29

Please sign in to comment.