From 85e982a6bf9bd82524baf53546b31d85b426fa62 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 14 Sep 2019 18:34:57 +0800 Subject: `InnerResponse` is now a type param instead of member, allowing better error messages through `cask.internal.Conversion`: ``` Cannot return java.io.ByteArrayInputStream as a cask.model.Response[cask.endpoints.JsonData] ``` --- cask/src/cask/endpoints/FormEndpoint.scala | 4 ++-- cask/src/cask/endpoints/JsonEndpoint.scala | 8 ++++---- cask/src/cask/endpoints/StaticEndpoints.scala | 7 +++---- cask/src/cask/endpoints/WebEndpoints.scala | 3 +-- cask/src/cask/endpoints/WebSocketEndpoint.scala | 4 ++-- cask/src/cask/internal/Conversion.scala | 4 ++-- cask/src/cask/main/Decorators.scala | 10 ++++------ cask/src/cask/main/Main.scala | 3 ++- cask/src/cask/main/Routes.scala | 8 ++++---- cask/src/cask/package.scala | 2 +- docs/pages/1 - Cask: a Scala HTTP micro-framework.md | 14 +++++++------- example/endpoints/app/src/Endpoints.scala | 3 +-- example/formJsonPost/app/src/FormJsonPost.scala | 5 +---- 13 files changed, 34 insertions(+), 41 deletions(-) diff --git a/cask/src/cask/endpoints/FormEndpoint.scala b/cask/src/cask/endpoints/FormEndpoint.scala index 436bed4..264c169 100644 --- a/cask/src/cask/endpoints/FormEndpoint.scala +++ b/cask/src/cask/endpoints/FormEndpoint.scala @@ -43,8 +43,8 @@ object FormReader{ def read(ctx: Request, label: String, input: Seq[FormEntry]) = input.map(_.asInstanceOf[FormFile]) } } -class postForm(val path: String, override val subpath: Boolean = false) extends Endpoint { - type InnerReturned = Response.Raw +class postForm(val path: String, override val subpath: Boolean = false) + extends Endpoint[Response.Raw] { val methods = Seq("post") type Input = Seq[FormEntry] diff --git a/cask/src/cask/endpoints/JsonEndpoint.scala b/cask/src/cask/endpoints/JsonEndpoint.scala index 3b3b095..2e3373f 100644 --- a/cask/src/cask/endpoints/JsonEndpoint.scala +++ b/cask/src/cask/endpoints/JsonEndpoint.scala @@ -39,8 +39,8 @@ object JsonData extends DataCompanion[JsonData]{ } } -class postJson(val path: String, override val subpath: Boolean = false) extends Endpoint{ - type InnerReturned = Response[JsonData] +class postJson(val path: String, override val subpath: Boolean = false) + extends Endpoint[Response[JsonData]]{ val methods = Seq("post") type Input = ujson.Value type InputParser[T] = JsReader[T] @@ -79,8 +79,8 @@ class postJson(val path: String, override val subpath: Boolean = false) extends def wrapPathSegment(s: String): Input = ujson.Str(s) } -class getJson(val path: String, override val subpath: Boolean = false) extends Endpoint{ - type InnerReturned = Response[JsonData] +class getJson(val path: String, override val subpath: Boolean = false) + extends Endpoint[Response[JsonData]]{ val methods = Seq("get") type Input = Seq[String] type InputParser[T] = QueryParamReader[T] diff --git a/cask/src/cask/endpoints/StaticEndpoints.scala b/cask/src/cask/endpoints/StaticEndpoints.scala index 401f845..bf99d09 100644 --- a/cask/src/cask/endpoints/StaticEndpoints.scala +++ b/cask/src/cask/endpoints/StaticEndpoints.scala @@ -3,8 +3,7 @@ package cask.endpoints import cask.main.Endpoint import cask.model.Request -class staticFiles(val path: String) extends Endpoint{ - type InnerReturned = String +class staticFiles(val path: String) extends Endpoint[String]{ val methods = Seq("get") type Input = Seq[String] type InputParser[T] = QueryParamReader[T] @@ -22,8 +21,8 @@ class staticFiles(val path: String) extends Endpoint{ def wrapPathSegment(s: String): Input = Seq(s) } -class staticResources(val path: String, resourceRoot: ClassLoader = getClass.getClassLoader) extends Endpoint{ - type InnerReturned = String +class staticResources(val path: String, resourceRoot: ClassLoader = getClass.getClassLoader) + extends Endpoint[String]{ val methods = Seq("get") type Input = Seq[String] type InputParser[T] = QueryParamReader[T] diff --git a/cask/src/cask/endpoints/WebEndpoints.scala b/cask/src/cask/endpoints/WebEndpoints.scala index 7cac4f5..b52b290 100644 --- a/cask/src/cask/endpoints/WebEndpoints.scala +++ b/cask/src/cask/endpoints/WebEndpoints.scala @@ -7,8 +7,7 @@ import cask.model.{Request, Response} import collection.JavaConverters._ -trait WebEndpoint extends Endpoint{ - type InnerReturned = Response.Raw +trait WebEndpoint extends Endpoint[Response.Raw]{ type Input = Seq[String] type InputParser[T] = QueryParamReader[T] def wrapFunction(ctx: Request, diff --git a/cask/src/cask/endpoints/WebSocketEndpoint.scala b/cask/src/cask/endpoints/WebSocketEndpoint.scala index 5f35832..ca2854f 100644 --- a/cask/src/cask/endpoints/WebSocketEndpoint.scala +++ b/cask/src/cask/endpoints/WebSocketEndpoint.scala @@ -13,8 +13,8 @@ object WebsocketResult{ implicit class Listener(val value: WebSocketConnectionCallback) extends WebsocketResult } -class websocket(val path: String, override val subpath: Boolean = false) extends cask.main.BaseEndpoint{ - type InnerReturned = WebsocketResult +class websocket(val path: String, override val subpath: Boolean = false) + extends cask.main.BaseEndpoint[WebsocketResult]{ val methods = Seq("websocket") type Input = Seq[String] type InputParser[T] = QueryParamReader[T] diff --git a/cask/src/cask/internal/Conversion.scala b/cask/src/cask/internal/Conversion.scala index e6a5a47..8d8ee3a 100644 --- a/cask/src/cask/internal/Conversion.scala +++ b/cask/src/cask/internal/Conversion.scala @@ -2,8 +2,8 @@ package cask.internal import scala.annotation.implicitNotFound -@implicitNotFound("Cannot return ${T} as a ${V} response") +@implicitNotFound("Cannot return ${T} as a ${V}") class Conversion[T, V](val f: T => V) object Conversion{ - def create[T, V](implicit f: T => V) = new Conversion(f) + implicit 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 28d44f5..e395d5b 100644 --- a/cask/src/cask/main/Decorators.scala +++ b/cask/src/cask/main/Decorators.scala @@ -9,7 +9,7 @@ import cask.model.{Request, Response} * Annotates a Cask endpoint that returns a HTTP [[Response]]; similar to a * [[Decorator]] but with additional metadata and capabilities. */ -trait Endpoint extends BaseEndpoint { +trait Endpoint[InnerReturned] extends BaseEndpoint[InnerReturned] { type OuterReturned = Router.Result[Response.Raw] } @@ -17,7 +17,7 @@ trait Endpoint extends BaseEndpoint { * An [[Endpoint]] that may return something else than a HTTP response, e.g. * a websocket endpoint which may instead return a websocket event handler */ -trait BaseEndpoint extends BaseDecorator{ +trait BaseEndpoint[InnerReturned] extends BaseDecorator[InnerReturned]{ /** * What is the path that this particular endpoint matches? */ @@ -55,10 +55,9 @@ trait BaseEndpoint extends BaseDecorator{ /** * A [[Decorator]] that may deal with values other than HTTP [[Response]]s */ -trait BaseDecorator{ +trait BaseDecorator[InnerReturned]{ type Input type InputParser[T] <: ArgReader[Input, T, Request] - type InnerReturned type Delegate = Map[String, Input] => Router.Result[InnerReturned] type OuterReturned <: Router.Result[Any] def wrapFunction(ctx: Request, delegate: Delegate): OuterReturned @@ -76,10 +75,9 @@ trait BaseDecorator{ * to `wrapFunction`, which takes a `Map` representing any additional argument * lists (if any). */ -trait Decorator extends BaseDecorator{ +trait Decorator extends BaseDecorator[Response.Raw]{ type OuterReturned = Router.Result[Response.Raw] type Input = Any - type InnerReturned = Response.Raw type InputParser[T] = NoOpParser[Input, T] } diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala index 7128006..c3a4ace 100644 --- a/cask/src/cask/main/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -94,7 +94,8 @@ abstract class BaseMain{ case head :: rest => head.wrapFunction( ctx, - args => rec(rest, args :: bindings).asInstanceOf[Router.Result[head.InnerReturned]] + args => rec(rest, args :: bindings) + .asInstanceOf[cask.internal.Router.Result[cask.model.Response.Raw]] ) case Nil => diff --git a/cask/src/cask/main/Routes.scala b/cask/src/cask/main/Routes.scala index a4660a9..5b02542 100644 --- a/cask/src/cask/main/Routes.scala +++ b/cask/src/cask/main/Routes.scala @@ -8,7 +8,7 @@ import language.experimental.macros object Routes{ case class EndpointMetadata[T](decorators: Seq[Decorator], - endpoint: BaseEndpoint, + endpoint: BaseEndpoint[_], entryPoint: EntryPoint[T, _]) case class RoutesEndpointsMetadata[T](value: EndpointMetadata[T]*) object RoutesEndpointsMetadata{ @@ -19,15 +19,15 @@ object Routes{ val routeParts = for{ m <- c.weakTypeOf[T].members - val annotations = m.annotations.filter(_.tree.tpe <:< c.weakTypeOf[BaseDecorator]).reverse + val annotations = m.annotations.filter(_.tree.tpe <:< c.weakTypeOf[BaseDecorator[_]]).reverse if annotations.nonEmpty } yield { - if(!(annotations.head.tree.tpe <:< weakTypeOf[BaseEndpoint])) c.abort( + if(!(annotations.head.tree.tpe <:< weakTypeOf[BaseEndpoint[_]])) c.abort( annotations.head.tree.pos, s"Last annotation applied to a function must be an instance of Endpoint, " + s"not ${annotations.head.tree.tpe}" ) - val allEndpoints = annotations.filter(_.tree.tpe <:< weakTypeOf[BaseEndpoint]) + val allEndpoints = annotations.filter(_.tree.tpe <:< weakTypeOf[BaseEndpoint[_]]) if(allEndpoints.length > 1) c.abort( annotations.head.tree.pos, s"You can only apply one Endpoint annotation to a function, not " + diff --git a/cask/src/cask/package.scala b/cask/src/cask/package.scala index 51aaaf6..c3909ec 100644 --- a/cask/src/cask/package.scala +++ b/cask/src/cask/package.scala @@ -38,6 +38,6 @@ package object cask { val Routes = main.Routes type Main = main.Main type Decorator = main.Decorator - type Endpoint = main.Endpoint + type Endpoint[InnerReturned] = main.Endpoint[InnerReturned] } diff --git a/docs/pages/1 - Cask: a Scala HTTP micro-framework.md b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md index 093c8c3..52653d2 100644 --- a/docs/pages/1 - Cask: a Scala HTTP micro-framework.md +++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework.md @@ -162,13 +162,13 @@ what to do in each case. You can use the `@cask.route` annotation to do so $$$formJsonPost If you need to handle a JSON-encoded POST request, you can use the -`@cask.postJson` decorator. This assumes the posted request body is a JSON dict, -and uses its keys to populate the endpoint's parameters, either as raw -`ujson.Js.Value`s or deserialized into `Seq[Int]`s or other things. -Deserialization is handled using the -[uPickle](https://github.com/lihaoyi/upickle) JSON library, though you could -write your own version of `postJson` to work with any other JSON library of your -choice. +`@cask.postJson` decorator. This assumes the posted request body is a +JSON dict, and uses its keys to populate the endpoint's parameters, +either as raw `ujson.Value`s or deserialized into `Seq[Int]`s or other +things. Deserialization is handled using the +[uPickle](https://github.com/lihaoyi/upickle) JSON library, though you +could write your own version of `postJson` to work with any other JSON +library of your choice. Similarly, you can mark endpoints as `@cask.postForm`, in which case the endpoints params will be taken from the form-encoded POST body either raw (as diff --git a/example/endpoints/app/src/Endpoints.scala b/example/endpoints/app/src/Endpoints.scala index b934769..97da526 100644 --- a/example/endpoints/app/src/Endpoints.scala +++ b/example/endpoints/app/src/Endpoints.scala @@ -1,8 +1,7 @@ package app -class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint{ +class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint[Int]{ type Output = Int - type InnerReturned = Int def wrapFunction(ctx: cask.Request, delegate: Delegate): OuterReturned = { delegate(Map()).map{num => cask.Response("Echo " + num, statusCode = num) diff --git a/example/formJsonPost/app/src/FormJsonPost.scala b/example/formJsonPost/app/src/FormJsonPost.scala index 6dc82d3..b994ac1 100644 --- a/example/formJsonPost/app/src/FormJsonPost.scala +++ b/example/formJsonPost/app/src/FormJsonPost.scala @@ -1,10 +1,7 @@ package app - -import java.io.ByteArrayInputStream - object FormJsonPost extends cask.MainRoutes{ @cask.postJson("/json") - def jsonEndpoint(value1: ujson.Js.Value, value2: Seq[Int]) = { + def jsonEndpoint(value1: ujson.Value, value2: Seq[Int]) = { "OK " + value1 + " " + value2 } -- cgit v1.2.3