From 2c1dcc3cd33fbd2c2c921f20f67c45ce48c1e8bc Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 14 Sep 2019 18:25:51 +0800 Subject: `cask.Response` is now covariant --- cask/src/cask/endpoints/JsonEndpoint.scala | 13 ++++++------- cask/src/cask/internal/Conversion.scala | 9 +++++++++ cask/src/cask/main/Decorators.scala | 7 +++++-- cask/src/cask/model/Response.scala | 31 ++++++++++++++++++++---------- 4 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 cask/src/cask/internal/Conversion.scala (limited to 'cask') diff --git a/cask/src/cask/endpoints/JsonEndpoint.scala b/cask/src/cask/endpoints/JsonEndpoint.scala index edf0c46..3b3b095 100644 --- a/cask/src/cask/endpoints/JsonEndpoint.scala +++ b/cask/src/cask/endpoints/JsonEndpoint.scala @@ -4,7 +4,9 @@ import java.io.{ByteArrayOutputStream, InputStream, OutputStream, OutputStreamWr import cask.internal.{Router, Util} import cask.main.Endpoint +import cask.model.Response.DataCompanion import cask.model.{Request, Response} + import collection.JavaConverters._ sealed trait JsReader[T] extends Router.ArgReader[ujson.Value, T, cask.model.Request] @@ -27,7 +29,7 @@ object JsReader{ } } trait JsonData extends Response.Data -object JsonData{ +object JsonData extends DataCompanion[JsonData]{ implicit class JsonDataImpl[T: upickle.default.Writer](t: T) extends JsonData{ def write(out: OutputStream) = { val writer = new OutputStreamWriter(out) @@ -71,7 +73,7 @@ class postJson(val path: String, override val subpath: Boolean = false) extends } yield obj.toMap obj match{ case Left(r) => Router.Result.Success(r.map(Response.Data.StringData)) - case Right(params) => delegate(params).map(_.data) + case Right(params) => delegate(params) } } def wrapPathSegment(s: String): Input = ujson.Str(s) @@ -83,12 +85,9 @@ class getJson(val path: String, override val subpath: Boolean = false) extends E type Input = Seq[String] type InputParser[T] = QueryParamReader[T] override type OuterReturned = Router.Result[Response.Raw] - def wrapFunction(ctx: Request, - delegate: Delegate): Router.Result[Response.Raw] = { - - val res = delegate(WebEndpoint.buildMapFromQueryParams(ctx)) + def wrapFunction(ctx: Request, delegate: Delegate): Router.Result[Response.Raw] = { - res.map(_.data) + delegate(WebEndpoint.buildMapFromQueryParams(ctx)) } def wrapPathSegment(s: String) = Seq(s) } \ No newline at end of file diff --git a/cask/src/cask/internal/Conversion.scala b/cask/src/cask/internal/Conversion.scala new file mode 100644 index 0000000..e6a5a47 --- /dev/null +++ b/cask/src/cask/internal/Conversion.scala @@ -0,0 +1,9 @@ +package cask.internal + +import scala.annotation.implicitNotFound + +@implicitNotFound("Cannot return ${T} as a ${V} response") +class Conversion[T, V](val f: T => V) +object Conversion{ + def create[T, V](implicit f: T => V) = new Conversion(f) +} diff --git a/cask/src/cask/main/Decorators.scala b/cask/src/cask/main/Decorators.scala index d2fc0c7..28d44f5 100644 --- a/cask/src/cask/main/Decorators.scala +++ b/cask/src/cask/main/Decorators.scala @@ -1,6 +1,6 @@ package cask.main -import cask.internal.Router +import cask.internal.{Conversion, Router} import cask.internal.Router.ArgReader import cask.model.{Request, Response} @@ -36,7 +36,10 @@ trait BaseEndpoint extends BaseDecorator{ */ def subpath: Boolean = false - def convertToResultType(t: InnerReturned): InnerReturned = t + def convertToResultType[T](t: T) + (implicit f: Conversion[T, InnerReturned]): InnerReturned = { + f.f(t) + } /** * [[Endpoint]]s are unique among decorators in that they alone can bind diff --git a/cask/src/cask/model/Response.scala b/cask/src/cask/model/Response.scala index 5b51689..e9ca672 100644 --- a/cask/src/cask/model/Response.scala +++ b/cask/src/cask/model/Response.scala @@ -12,7 +12,7 @@ import cask.internal.Util * bytes, uPickle JSON-convertable types or arbitrary input streams. You can * also construct your own implementations of `Response.Data`. */ -case class Response[T]( +case class Response[+T]( data: T, statusCode: Int, headers: Seq[(String, String)], @@ -20,23 +20,33 @@ case class Response[T]( ){ def map[V](f: T => V) = new Response(f(data), statusCode, headers, cookies) } -object Response{ + +object Response { type Raw = Response[Data] def apply[T](data: T, statusCode: Int = 200, headers: Seq[(String, String)] = Nil, cookies: Seq[Cookie] = Nil) = new Response(data, statusCode, headers, cookies) - - implicit def dataResponse[T, V](t: T)(implicit c: T => V): Response[V] = { - Response[V](t) - } - implicit def dataResponse2[T, V](t: Response[T])(implicit c: T => V): Response[V] = { - t.map(c) - } trait Data{ def write(out: OutputStream): Unit } - object Data{ + trait DataCompanion[V]{ + // Put the implicit constructors for Response[Data] into the `Data` companion + // object and all subclasses of `Data`, because for some reason putting them in + // the `Response` companion object doesn't work properly. For the same unknown + // reasons, we cannot have `dataResponse` and `dataResponse2` take two type + // params T and V, and instead have to embed the implicit target type as a + // parameter of the enclosing trait + + implicit def dataResponse[T](t: T)(implicit c: T => V): Response[V] = { + Response(c(t)) + } + + implicit def dataResponse2[T](t: Response[T])(implicit c: T => V): Response[V] = { + t.map(c(_)) + } + } + object Data extends DataCompanion[Data]{ implicit class UnitData(s: Unit) extends Data{ def write(out: OutputStream) = () } @@ -57,6 +67,7 @@ object Response{ } } } + object Redirect{ def apply(url: String) = Response("", 301, Seq("Location" -> url), Nil) } -- cgit v1.2.3