diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2019-09-14 20:44:03 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-09-14 20:44:03 +0800 |
commit | dedeed376e1e906ec2eb1574a73f08c24aba47c8 (patch) | |
tree | 337f0528e9284759800228e46f649cbc01f5edfe | |
parent | 90b6806e5fb91b207f9d8e2da2a58c25928badea (diff) | |
parent | edd1f62ce364f8b559431249e2350c2bb75fccec (diff) | |
download | cask-dedeed376e1e906ec2eb1574a73f08c24aba47c8.tar.gz cask-dedeed376e1e906ec2eb1574a73f08c24aba47c8.tar.bz2 cask-dedeed376e1e906ec2eb1574a73f08c24aba47c8.zip |
Merge pull request #13 from lihaoyi-databricks/wip0.2.3
Upstream some fixes
62 files changed, 302 insertions, 218 deletions
@@ -50,7 +50,7 @@ object cask extends ScalaModule with PublishModule { def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", // ivy"org.xerial:sqlite-jdbc:3.18.0", // ivy"io.getquill::quill-jdbc:2.6.0" diff --git a/cask/src/cask/decorators/compress.scala b/cask/src/cask/decorators/compress.scala index 75e2bed..7a7eefb 100644 --- a/cask/src/cask/decorators/compress.scala +++ b/cask/src/cask/decorators/compress.scala @@ -6,7 +6,7 @@ import cask.internal.Router import cask.model.{Request, Response} import collection.JavaConverters._ -class compress extends cask.Decorator{ +class compress extends cask.RawDecorator{ def wrapFunction(ctx: Request, delegate: Delegate) = { val acceptEncodings = ctx.exchange.getRequestHeaders.get("Accept-Encoding").asScala.flatMap(_.split(", ")) delegate(Map()).map{ v => diff --git a/cask/src/cask/endpoints/FormEndpoint.scala b/cask/src/cask/endpoints/FormEndpoint.scala index 471c5e5..6f65786 100644 --- a/cask/src/cask/endpoints/FormEndpoint.scala +++ b/cask/src/cask/endpoints/FormEndpoint.scala @@ -1,7 +1,7 @@ package cask.endpoints import cask.internal.{Router, Util} -import cask.main.Endpoint +import cask.main.HttpEndpoint import cask.model._ import io.undertow.server.handlers.form.FormParserFactory @@ -43,14 +43,13 @@ 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 Output = Response +class postForm(val path: String, override val subpath: Boolean = false) + extends HttpEndpoint[Response.Raw, Seq[FormEntry]] { val methods = Seq("post") - type Input = Seq[FormEntry] type InputParser[T] = FormReader[T] def wrapFunction(ctx: Request, - delegate: Map[String, Input] => Router.Result[Output]): Router.Result[Response] = { + delegate: Delegate): Router.Result[Response.Raw] = { try { val formData = FormParserFactory.builder().build().createParser(ctx.exchange).parseBlocking() delegate( @@ -62,11 +61,12 @@ class postForm(val path: String, override val subpath: Boolean = false) extends ) } catch{case e: Exception => Router.Result.Success(cask.model.Response( - "Unable to parse form data: " + e + "\n" + Util.stackTraceString(e) + "Unable to parse form data: " + e + "\n" + Util.stackTraceString(e), + statusCode = 400 )) } } - def wrapPathSegment(s: String): Input = Seq(FormValue(s, new io.undertow.util.HeaderMap)) + def wrapPathSegment(s: String): Seq[FormEntry] = Seq(FormValue(s, new io.undertow.util.HeaderMap)) } diff --git a/cask/src/cask/endpoints/JsonEndpoint.scala b/cask/src/cask/endpoints/JsonEndpoint.scala index e0d1257..6d3db82 100644 --- a/cask/src/cask/endpoints/JsonEndpoint.scala +++ b/cask/src/cask/endpoints/JsonEndpoint.scala @@ -1,11 +1,13 @@ package cask.endpoints -import java.io.ByteArrayOutputStream +import java.io.{ByteArrayOutputStream, InputStream, OutputStream, OutputStreamWriter} import cask.internal.{Router, Util} -import cask.main.Endpoint +import cask.main.HttpEndpoint +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] object JsReader{ @@ -26,13 +28,24 @@ object JsReader{ } } } -class postJson(val path: String, override val subpath: Boolean = false) extends Endpoint{ - type Output = Response +trait JsonData extends Response.Data +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) + implicitly[upickle.default.Writer[T]].write(new ujson.BaseRenderer(writer), t) + writer.flush() + } + } +} + +class postJson(val path: String, override val subpath: Boolean = false) + extends HttpEndpoint[Response[JsonData], ujson.Value]{ val methods = Seq("post") - type Input = ujson.Js.Value type InputParser[T] = JsReader[T] + override type OuterReturned = Router.Result[Response.Raw] def wrapFunction(ctx: Request, - delegate: Map[String, Input] => Router.Result[Output]): Router.Result[Response] = { + delegate: Delegate): Router.Result[Response.Raw] = { val obj = for{ str <- try { @@ -41,21 +54,38 @@ class postJson(val path: String, override val subpath: Boolean = false) extends Right(new String(boas.toByteArray)) } catch{case e: Throwable => Left(cask.model.Response( - "Unable to deserialize input JSON text: " + e + "\n" + Util.stackTraceString(e) + "Unable to deserialize input JSON text: " + e + "\n" + Util.stackTraceString(e), + statusCode = 400 ))} json <- try Right(ujson.read(str)) catch{case e: Throwable => Left(cask.model.Response( - "Input text is invalid JSON: " + e + "\n" + Util.stackTraceString(e) + "Input text is invalid JSON: " + e + "\n" + Util.stackTraceString(e), + statusCode = 400 ))} obj <- try Right(json.obj) - catch {case e: Throwable => Left(cask.model.Response("Input JSON must be a dictionary"))} + catch {case e: Throwable => Left(cask.model.Response( + "Input JSON must be a dictionary", + statusCode = 400 + ))} } yield obj.toMap obj match{ - case Left(r) => Router.Result.Success(r) + case Left(r) => Router.Result.Success(r.map(Response.Data.StringData)) case Right(params) => delegate(params) } } - def wrapPathSegment(s: String): Input = ujson.Js.Str(s) + def wrapPathSegment(s: String): ujson.Value = ujson.Str(s) } + +class getJson(val path: String, override val subpath: Boolean = false) + extends HttpEndpoint[Response[JsonData], Seq[String]]{ + val methods = Seq("get") + type InputParser[T] = QueryParamReader[T] + override type OuterReturned = Router.Result[Response.Raw] + def wrapFunction(ctx: Request, delegate: Delegate): Router.Result[Response.Raw] = { + + delegate(WebEndpoint.buildMapFromQueryParams(ctx)) + } + def wrapPathSegment(s: String) = Seq(s) +}
\ No newline at end of file diff --git a/cask/src/cask/endpoints/StaticEndpoints.scala b/cask/src/cask/endpoints/StaticEndpoints.scala index fd194ca..0abfcf5 100644 --- a/cask/src/cask/endpoints/StaticEndpoints.scala +++ b/cask/src/cask/endpoints/StaticEndpoints.scala @@ -1,15 +1,13 @@ package cask.endpoints -import cask.main.Endpoint +import cask.main.HttpEndpoint import cask.model.Request -class staticFiles(val path: String) extends Endpoint{ - type Output = String +class staticFiles(val path: String) extends HttpEndpoint[String, Seq[String]]{ val methods = Seq("get") - type Input = Seq[String] type InputParser[T] = QueryParamReader[T] override def subpath = true - def wrapFunction(ctx: Request, delegate: Delegate): Returned = { + def wrapFunction(ctx: Request, delegate: Delegate): OuterReturned = { delegate(Map()).map(t => cask.model.StaticFile( (cask.internal.Util.splitPath(t) ++ ctx.remainingPathSegments) @@ -19,16 +17,15 @@ class staticFiles(val path: String) extends Endpoint{ ) } - def wrapPathSegment(s: String): Input = Seq(s) + def wrapPathSegment(s: String): Seq[String] = Seq(s) } -class staticResources(val path: String, resourceRoot: ClassLoader = getClass.getClassLoader) extends Endpoint{ - type Output = String +class staticResources(val path: String, resourceRoot: ClassLoader = getClass.getClassLoader) + extends HttpEndpoint[String, Seq[String]]{ val methods = Seq("get") - type Input = Seq[String] type InputParser[T] = QueryParamReader[T] override def subpath = true - def wrapFunction(ctx: Request, delegate: Delegate): Returned = { + def wrapFunction(ctx: Request, delegate: Delegate): OuterReturned = { delegate(Map()).map(t => cask.model.StaticResource( (cask.internal.Util.splitPath(t) ++ ctx.remainingPathSegments) @@ -39,5 +36,5 @@ class staticResources(val path: String, resourceRoot: ClassLoader = getClass.get ) } - def wrapPathSegment(s: String): Input = Seq(s) + def wrapPathSegment(s: String): Seq[String] = Seq(s) } diff --git a/cask/src/cask/endpoints/WebEndpoints.scala b/cask/src/cask/endpoints/WebEndpoints.scala index ab3b480..8bb3bae 100644 --- a/cask/src/cask/endpoints/WebEndpoints.scala +++ b/cask/src/cask/endpoints/WebEndpoints.scala @@ -1,19 +1,22 @@ package cask.endpoints import cask.internal.Router -import cask.main.Endpoint +import cask.main.HttpEndpoint import cask.model.{Request, Response} import collection.JavaConverters._ -trait WebEndpoint extends Endpoint{ - type Output = Response - type Input = Seq[String] +trait WebEndpoint extends HttpEndpoint[Response.Raw, Seq[String]]{ type InputParser[T] = QueryParamReader[T] def wrapFunction(ctx: Request, - delegate: Map[String, Input] => Router.Result[Output]): Router.Result[Response] = { - + delegate: Delegate): Router.Result[Response.Raw] = { + delegate(WebEndpoint.buildMapFromQueryParams(ctx)) + } + def wrapPathSegment(s: String) = Seq(s) +} +object WebEndpoint{ + def buildMapFromQueryParams(ctx: Request) = { val b = Map.newBuilder[String, Seq[String]] val queryParams = ctx.exchange.getQueryParameters for(k <- queryParams.keySet().iterator().asScala){ @@ -22,9 +25,8 @@ trait WebEndpoint extends Endpoint{ deque.toArray(arr) b += (k -> (arr: Seq[String])) } - delegate(b.result()) + b.result() } - def wrapPathSegment(s: String) = Seq(s) } class get(val path: String, override val subpath: Boolean = false) extends WebEndpoint{ val methods = Seq("get") diff --git a/cask/src/cask/endpoints/WebSocketEndpoint.scala b/cask/src/cask/endpoints/WebSocketEndpoint.scala index f747341..842d508 100644 --- a/cask/src/cask/endpoints/WebSocketEndpoint.scala +++ b/cask/src/cask/endpoints/WebSocketEndpoint.scala @@ -3,22 +3,24 @@ package cask.endpoints import cask.internal.Router import cask.model.Request import io.undertow.websockets.WebSocketConnectionCallback - +import collection.JavaConverters._ sealed trait WebsocketResult object WebsocketResult{ - implicit class Response(val value: cask.model.Response) extends WebsocketResult + implicit class Response[T](value0: cask.model.Response[T]) + (implicit f: T => cask.model.Response.Data) extends WebsocketResult{ + def value = value0.map(f) + } implicit class Listener(val value: WebSocketConnectionCallback) extends WebsocketResult } -class websocket(val path: String, override val subpath: Boolean = false) extends cask.main.BaseEndpoint{ - type Output = WebsocketResult +class websocket(val path: String, override val subpath: Boolean = false) + extends cask.main.Endpoint[WebsocketResult, Seq[String]]{ val methods = Seq("websocket") - type Input = Seq[String] type InputParser[T] = QueryParamReader[T] - type Returned = Router.Result[WebsocketResult] - def wrapFunction(ctx: Request, delegate: Delegate): Returned = delegate(Map()) - - def wrapPathSegment(s: String): Input = Seq(s) - + type OuterReturned = Router.Result[WebsocketResult] + def wrapFunction(ctx: Request, delegate: Delegate): OuterReturned = { + delegate(WebEndpoint.buildMapFromQueryParams(ctx)) + } + def wrapPathSegment(s: String): Seq[String] = Seq(s) } diff --git a/cask/src/cask/internal/Conversion.scala b/cask/src/cask/internal/Conversion.scala new file mode 100644 index 0000000..8d8ee3a --- /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}") +class Conversion[T, V](val f: T => V) +object Conversion{ + implicit def create[T, V](implicit f: T => V) = new Conversion(f) +} diff --git a/cask/src/cask/internal/Util.scala b/cask/src/cask/internal/Util.scala index 3f7ab61..87e2a15 100644 --- a/cask/src/cask/internal/Util.scala +++ b/cask/src/cask/internal/Util.scala @@ -7,9 +7,14 @@ import scala.collection.mutable import java.io.OutputStream import scala.annotation.switch +import scala.concurrent.{ExecutionContext, Future, Promise} object Util { - + def firstFutureOf[T](futures: Seq[Future[T]])(implicit ec: ExecutionContext) = { + val p = Promise[T] + futures.foreach(_.foreach(p.trySuccess)) + p.future + } /** * Convert a string to a C&P-able literal. Basically * copied verbatim from the uPickle source code. diff --git a/cask/src/cask/main/Decorators.scala b/cask/src/cask/main/Decorators.scala index 7aa361f..573c139 100644 --- a/cask/src/cask/main/Decorators.scala +++ b/cask/src/cask/main/Decorators.scala @@ -1,18 +1,44 @@ package cask.main -import cask.internal.Router +import cask.internal.{Conversion, Router} import cask.internal.Router.ArgReader import cask.model.{Request, Response} +/** + * A [[Decorator]] allows you to annotate a function to wrap it, via + * `wrapFunction`. You can use this to perform additional validation before or + * after the function runs, provide an additional parameter list of params, + * open/commit/rollback database transactions before/after the function runs, + * or even retrying the wrapped function if it fails. + * + * Calls to the wrapped function are done on the `delegate` parameter passed + * to `wrapFunction`, which takes a `Map` representing any additional argument + * lists (if any). + */ +trait Decorator[InnerReturned, Input]{ + final type InputTypeAlias = Input + type InputParser[T] <: ArgReader[Input, T, Request] + final type Delegate = Map[String, Input] => Router.Result[InnerReturned] + type OuterReturned <: Router.Result[Any] + def wrapFunction(ctx: Request, delegate: Delegate): OuterReturned + def getParamParser[T](implicit p: InputParser[T]) = p +} -trait Endpoint extends BaseEndpoint { - type Returned = Router.Result[Response] +/** + * A [[RawDecorator]] is a decorator that operates on the raw request and + * response stream, before and after the primary [[Endpoint]] does it's job. + */ +trait RawDecorator extends Decorator[Response.Raw, Any]{ + type OuterReturned = Router.Result[Response.Raw] + type InputParser[T] = NoOpParser[Any, T] } + + /** - * Used to annotate a single Cask endpoint function; similar to a [[Decorator]] - * but with additional metadata and capabilities. + * An [[HttpEndpoint]] 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 Endpoint[InnerReturned, Input] extends Decorator[InnerReturned, Input]{ /** * What is the path that this particular endpoint matches? */ @@ -31,10 +57,13 @@ trait BaseEndpoint extends BaseDecorator{ */ def subpath: Boolean = false - def convertToResultType(t: Output): Output = 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 + * [[HttpEndpoint]]s are unique among decorators in that they alone can bind * path segments to parameters, e.g. binding `/hello/:world` to `(world: Int)`. * In order to do so, we need to box up the path segment strings into an * [[Input]] so they can later be parsed by [[getParamParser]] into an @@ -44,34 +73,15 @@ trait BaseEndpoint extends BaseDecorator{ } -trait BaseDecorator{ - type Input - type InputParser[T] <: ArgReader[Input, T, Request] - type Output - type Delegate = Map[String, Input] => Router.Result[Output] - type Returned <: Router.Result[Any] - def wrapFunction(ctx: Request, delegate: Delegate): Returned - def getParamParser[T](implicit p: InputParser[T]) = p -} - /** - * A decorator allows you to annotate a function to wrap it, via - * `wrapFunction`. You can use this to perform additional validation before or - * after the function runs, provide an additional parameter list of params, - * open/commit/rollback database transactions before/after the function runs, - * or even retrying the wrapped function if it fails. - * - * Calls to the wrapped function are done on the `delegate` parameter passed - * to `wrapFunction`, which takes a `Map` representing any additional argument - * lists (if any). + * Annotates a Cask endpoint that returns a HTTP [[Response]]; similar to a + * [[RawDecorator]] but with additional metadata and capabilities. */ -trait Decorator extends BaseDecorator{ - type Returned = Router.Result[Response] - type Input = Any - type Output = Response - type InputParser[T] = NoOpParser[Input, T] +trait HttpEndpoint[InnerReturned, Input] extends Endpoint[InnerReturned, Input] { + type OuterReturned = Router.Result[Response.Raw] } + class NoOpParser[Input, T] extends ArgReader[Input, T, Request] { def arity = 1 diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala index 88d7a61..32e6517 100644 --- a/cask/src/cask/main/Main.scala +++ b/cask/src/cask/main/Main.scala @@ -28,7 +28,7 @@ class Main(servers0: Routes*) extends BaseMain{ def allRoutes = servers0.toSeq } abstract class BaseMain{ - def mainDecorators = Seq.empty[cask.main.Decorator] + def mainDecorators = Seq.empty[cask.main.RawDecorator] def allRoutes: Seq[Routes] def port: Int = 8080 def host: String = "localhost" @@ -48,7 +48,7 @@ abstract class BaseMain{ ) }.toMap - def writeResponse(exchange: HttpServerExchange, response: Response) = { + def writeResponse(exchange: HttpServerExchange, response: Response.Raw) = { response.headers.foreach{case (k, v) => exchange.getResponseHeaders.put(new HttpString(k), v) } @@ -58,7 +58,7 @@ abstract class BaseMain{ response.data.write(exchange.getOutputStream) } - def handleNotFound(): Response = { + def handleNotFound(): Response.Raw = { Response( s"Error 404: ${Status.codesToStatus(404).reason}", statusCode = 404 @@ -68,36 +68,38 @@ abstract class BaseMain{ def defaultHandler = new BlockingHandler( new HttpHandler() { - def handleRequest(exchange: HttpServerExchange): Unit = { + def handleRequest(exchange: HttpServerExchange): Unit = try { +// println("Handling Request: " + exchange.getRequestPath) val (effectiveMethod, runner) = if (exchange.getRequestHeaders.getFirst("Upgrade") == "websocket") { "websocket" -> ((r: Any) => r.asInstanceOf[WebsocketResult] match{ case l: WebsocketResult.Listener => io.undertow.Handlers.websocket(l.value).handleRequest(exchange) - case r: WebsocketResult.Response => + case r: WebsocketResult.Response[_] => writeResponseHandler(r).handleRequest(exchange) } - ) + ) } else ( exchange.getRequestMethod.toString.toLowerCase(), - (r: Any) => writeResponse(exchange, r.asInstanceOf[Response]) + (r: Any) => writeResponse(exchange, r.asInstanceOf[Response.Raw]) ) routeTries(effectiveMethod).lookup(Util.splitPath(exchange.getRequestPath).toList, Map()) match { case None => writeResponse(exchange, handleNotFound()) case Some(((routes, metadata), routeBindings, remaining)) => val ctx = Request(exchange, remaining) - def rec(remaining: List[Decorator], + def rec(remaining: List[RawDecorator], bindings: List[Map[String, Any]]): Router.Result[Any] = try { remaining match { case head :: rest => head.wrapFunction( ctx, - args => rec(rest, args :: bindings).asInstanceOf[Router.Result[head.Output]] + args => rec(rest, args :: bindings) + .asInstanceOf[cask.internal.Router.Result[cask.model.Response.Raw]] ) case Nil => - metadata.endpoint.wrapFunction(ctx, endpointBindings => + metadata.endpoint.wrapFunction(ctx, (endpointBindings: Map[String, Any]) => metadata.entryPoint .asInstanceOf[EntryPoint[cask.main.Routes, cask.model.Request]] .invoke( @@ -116,16 +118,21 @@ abstract class BaseMain{ rec((metadata.decorators ++ routes.decorators ++ mainDecorators).toList, Nil)match{ case Router.Result.Success(res) => runner(res) case e: Router.Result.Error => - writeResponse(exchange, handleEndpointError(exchange, routes, metadata, e)) + writeResponse( + exchange, + handleEndpointError(exchange, routes, metadata, e).map(Response.Data.StringData) + ) None } } - +// println("Completed Request: " + exchange.getRequestPath) + }catch{case e: Throwable => + e.printStackTrace() } } ) - def writeResponseHandler(r: WebsocketResult.Response) = new BlockingHandler( + def writeResponseHandler(r: WebsocketResult.Response[_]) = new BlockingHandler( new HttpHandler { def handleRequest(exchange: HttpServerExchange): Unit = { writeResponse(exchange, r.value) @@ -142,15 +149,15 @@ abstract class BaseMain{ case _: Router.Result.Error.InvalidArguments => 400 case _: Router.Result.Error.MismatchedArguments => 400 } - Response( + val str = if (!debugMode) s"Error $statusCode: ${Status.codesToStatus(statusCode).reason}" else ErrorMsgs.formatInvokeError( routes, metadata.entryPoint.asInstanceOf[EntryPoint[cask.main.Routes, _]], e - ), - statusCode = statusCode - ) + ) + println(str) + Response(str, statusCode = statusCode) } diff --git a/cask/src/cask/main/Routes.scala b/cask/src/cask/main/Routes.scala index a4660a9..f133fdb 100644 --- a/cask/src/cask/main/Routes.scala +++ b/cask/src/cask/main/Routes.scala @@ -7,8 +7,8 @@ import scala.reflect.macros.blackbox.Context import language.experimental.macros object Routes{ - case class EndpointMetadata[T](decorators: Seq[Decorator], - endpoint: BaseEndpoint, + case class EndpointMetadata[T](decorators: Seq[RawDecorator], + endpoint: Endpoint[_, _], 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[Decorator[_, _]]).reverse if annotations.nonEmpty } yield { - if(!(annotations.head.tree.tpe <:< weakTypeOf[BaseEndpoint])) c.abort( + if(!(annotations.head.tree.tpe <:< weakTypeOf[Endpoint[_, _]])) 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[Endpoint[_, _]]) if(allEndpoints.length > 1) c.abort( annotations.head.tree.pos, s"You can only apply one Endpoint annotation to a function, not " + @@ -45,8 +45,7 @@ object Routes{ q"${annotObjectSyms.head}.convertToResultType", tq"cask.Request", annotObjectSyms.map(annotObjectSym => q"$annotObjectSym.getParamParser"), - annotObjectSyms.map(annotObjectSym => tq"$annotObjectSym.Input") - + annotObjectSyms.map(annotObjectSym => tq"$annotObjectSym.InputTypeAlias") ) val declarations = @@ -71,7 +70,7 @@ object Routes{ trait Routes{ - def decorators = Seq.empty[cask.main.Decorator] + def decorators = Seq.empty[cask.main.RawDecorator] private[this] var metadata0: Routes.RoutesEndpointsMetadata[this.type] = null def caskMetadata = if (metadata0 != null) metadata0 diff --git a/cask/src/cask/model/Response.scala b/cask/src/cask/model/Response.scala index 59b44c9..e9ca672 100644 --- a/cask/src/cask/model/Response.scala +++ b/cask/src/cask/model/Response.scala @@ -4,6 +4,7 @@ import java.io.{InputStream, OutputStream, OutputStreamWriter} import cask.internal.Util + /** * The basic response returned by a HTTP endpoint. * @@ -11,40 +12,62 @@ 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( - data: Response.Data, +case class Response[+T]( + data: T, statusCode: Int, headers: Seq[(String, String)], cookies: Seq[Cookie] -) -object Response{ - def apply(data: Data, - statusCode: Int = 200, - headers: Seq[(String, String)] = Nil, - cookies: Seq[Cookie] = Nil) = new Response(data, statusCode, headers, cookies) +){ + def map[V](f: T => V) = new Response(f(data), statusCode, headers, cookies) +} - implicit def dataResponse[T](t: T)(implicit c: T => Data) = Response(t) +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) 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) = () + } implicit class StringData(s: String) extends Data{ def write(out: OutputStream) = out.write(s.getBytes) } + implicit class NumericData[T: Numeric](s: T) extends Data{ + def write(out: OutputStream) = out.write(s.toString.getBytes) + } + implicit class BooleanData(s: Boolean) extends Data{ + def write(out: OutputStream) = out.write(s.toString.getBytes) + } implicit class BytesData(b: Array[Byte]) extends Data{ def write(out: OutputStream) = out.write(b) } implicit class StreamData(b: InputStream) extends Data{ def write(out: OutputStream) = Util.transferTo(b, out) } - implicit def JsonResponse[T: upickle.default.Writer](t: T) = new Data{ - def write(out: OutputStream) = implicitly[upickle.default.Writer[T]].write( - new ujson.BaseRenderer(new OutputStreamWriter(out)), - t - ) - } } } + object Redirect{ def apply(url: String) = Response("", 301, Seq("Location" -> url), Nil) } diff --git a/cask/src/cask/package.scala b/cask/src/cask/package.scala index 405bab7..d17cfe6 100644 --- a/cask/src/cask/package.scala +++ b/cask/src/cask/package.scala @@ -1,6 +1,6 @@ package object cask { // model - type Response = model.Response + type Response[T] = model.Response[T] val Response = model.Response val Abort = model.Abort val Redirect = model.Redirect @@ -29,6 +29,7 @@ package object cask { type staticFiles = endpoints.staticFiles type staticResources = endpoints.staticResources type postJson = endpoints.postJson + type getJson = endpoints.getJson type postForm = endpoints.postForm // main @@ -36,7 +37,7 @@ package object cask { type Routes = main.Routes val Routes = main.Routes type Main = main.Main - type Decorator = main.Decorator - type Endpoint = main.Endpoint + type RawDecorator = main.RawDecorator + type HttpEndpoint[InnerReturned, Input] = main.HttpEndpoint[InnerReturned, Input] } diff --git a/cask/test/src/test/cask/FailureTests.scala b/cask/test/src/test/cask/FailureTests.scala index d24d52c..6b01ec1 100644 --- a/cask/test/src/test/cask/FailureTests.scala +++ b/cask/test/src/test/cask/FailureTests.scala @@ -4,8 +4,8 @@ import cask.model.Request import utest._ object FailureTests extends TestSuite { - class myDecorator extends cask.Decorator { - def wrapFunction(ctx: Request, delegate: Delegate): Returned = { + class myDecorator extends cask.RawDecorator { + def wrapFunction(ctx: Request, delegate: Delegate): OuterReturned = { delegate(Map("extra" -> 31337)) } } 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/compress/app/test/src/ExampleTests.scala b/example/compress/app/test/src/ExampleTests.scala index 5a4a5bf..cd4d8d0 100644 --- a/example/compress/app/test/src/ExampleTests.scala +++ b/example/compress/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Compress - test(Compress){ host => + test("Compress") - withServer(Compress){ host => val expected = "Hello World! Hello World! Hello World!" requests.get(s"$host").text() ==> expected assert( diff --git a/example/compress/build.sc b/example/compress/build.sc index ac96e15..5e00fc0 100644 --- a/example/compress/build.sc +++ b/example/compress/build.sc @@ -10,7 +10,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/compress2/app/test/src/ExampleTests.scala b/example/compress2/app/test/src/ExampleTests.scala index cbe3301..9f9b4b3 100644 --- a/example/compress2/app/test/src/ExampleTests.scala +++ b/example/compress2/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Compress2Main - test(Compress2Main) { host => + test("Compress2Main") - withServer(Compress2Main) { host => val expected = "Hello World! Hello World! Hello World!" requests.get(s"$host").text() ==> expected assert( diff --git a/example/compress2/build.sc b/example/compress2/build.sc index c0e75a7..e4c2108 100644 --- a/example/compress2/build.sc +++ b/example/compress2/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/compress3/app/test/src/ExampleTests.scala b/example/compress3/app/test/src/ExampleTests.scala index 3b013f8..88e1cbf 100644 --- a/example/compress3/app/test/src/ExampleTests.scala +++ b/example/compress3/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Compress3Main - test(Compress3Main){ host => + test("Compress3Main") - withServer(Compress3Main){ host => val expected = "Hello World! Hello World! Hello World!" requests.get(s"$host").text() ==> expected assert( diff --git a/example/compress3/build.sc b/example/compress3/build.sc index c0e75a7..e4c2108 100644 --- a/example/compress3/build.sc +++ b/example/compress3/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/cookies/app/test/src/ExampleTests.scala b/example/cookies/app/test/src/ExampleTests.scala index 951728b..150f59a 100644 --- a/example/cookies/app/test/src/ExampleTests.scala +++ b/example/cookies/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Cookies - test(Cookies){ host => + test("Cookies") - withServer(Cookies){ host => val sess = requests.Session() sess.get(s"$host/read-cookie").statusCode ==> 400 sess.get(s"$host/store-cookie") diff --git a/example/cookies/build.sc b/example/cookies/build.sc index c0e75a7..e4c2108 100644 --- a/example/cookies/build.sc +++ b/example/cookies/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/decorated/app/src/Decorated.scala b/example/decorated/app/src/Decorated.scala index f037c6a..a88bd2e 100644 --- a/example/decorated/app/src/Decorated.scala +++ b/example/decorated/app/src/Decorated.scala @@ -3,13 +3,13 @@ object Decorated extends cask.MainRoutes{ class User{ override def toString = "[haoyi]" } - class loggedIn extends cask.Decorator { - def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { + class loggedIn extends cask.RawDecorator { + def wrapFunction(ctx: cask.Request, delegate: Delegate): OuterReturned = { delegate(Map("user" -> new User())) } } - class withExtra extends cask.Decorator { - def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { + class withExtra extends cask.RawDecorator { + def wrapFunction(ctx: cask.Request, delegate: Delegate): OuterReturned = { delegate(Map("extra" -> 31337)) } } diff --git a/example/decorated/app/test/src/ExampleTests.scala b/example/decorated/app/test/src/ExampleTests.scala index 9aea3bc..c2c19a6 100644 --- a/example/decorated/app/test/src/ExampleTests.scala +++ b/example/decorated/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Decorated - test(Decorated){ host => + test("Decorated") - withServer(Decorated){ host => requests.get(s"$host/hello/woo").text() ==> "woo31337" requests.get(s"$host/internal/boo").text() ==> "boo[haoyi]" requests.get(s"$host/internal-extra/goo").text() ==> "goo[haoyi]31337" diff --git a/example/decorated/build.sc b/example/decorated/build.sc index c0e75a7..e4c2108 100644 --- a/example/decorated/build.sc +++ b/example/decorated/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/decorated2/app/src/Decorated2.scala b/example/decorated2/app/src/Decorated2.scala index 20526b7..305281a 100644 --- a/example/decorated2/app/src/Decorated2.scala +++ b/example/decorated2/app/src/Decorated2.scala @@ -3,13 +3,13 @@ object Decorated2 extends cask.MainRoutes{ class User{ override def toString = "[haoyi]" } - class loggedIn extends cask.Decorator { - def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { + class loggedIn extends cask.RawDecorator { + def wrapFunction(ctx: cask.Request, delegate: Delegate): OuterReturned = { delegate(Map("user" -> new User())) } } - class withExtra extends cask.Decorator { - def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { + class withExtra extends cask.RawDecorator { + def wrapFunction(ctx: cask.Request, delegate: Delegate): OuterReturned = { delegate(Map("extra" -> 31337)) } } diff --git a/example/decorated2/app/test/src/ExampleTests.scala b/example/decorated2/app/test/src/ExampleTests.scala index 7fec82a..4343097 100644 --- a/example/decorated2/app/test/src/ExampleTests.scala +++ b/example/decorated2/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Decorated2 - test(Decorated2){ host => + test("Decorated2") - withServer(Decorated2){ host => requests.get(s"$host/hello/woo").text() ==> "woo31337" requests.get(s"$host/internal-extra/goo").text() ==> "goo[haoyi]31337" requests.get(s"$host/ignore-extra/boo").text() ==> "boo[haoyi]" diff --git a/example/decorated2/build.sc b/example/decorated2/build.sc index c0e75a7..e4c2108 100644 --- a/example/decorated2/build.sc +++ b/example/decorated2/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/endpoints/app/src/Endpoints.scala b/example/endpoints/app/src/Endpoints.scala index b478c80..9722673 100644 --- a/example/endpoints/app/src/Endpoints.scala +++ b/example/endpoints/app/src/Endpoints.scala @@ -1,8 +1,8 @@ package app -class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint{ - type Output = Int - def wrapFunction(ctx: cask.Request, delegate: Delegate): Returned = { +class custom(val path: String, val methods: Seq[String]) + extends cask.HttpEndpoint[Int, Seq[String]]{ + def wrapFunction(ctx: cask.Request, delegate: Delegate): OuterReturned = { delegate(Map()).map{num => cask.Response("Echo " + num, statusCode = num) } @@ -10,7 +10,6 @@ class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint{ def wrapPathSegment(s: String) = Seq(s) - type Input = Seq[String] type InputParser[T] = cask.endpoints.QueryParamReader[T] } diff --git a/example/endpoints/app/test/src/ExampleTests.scala b/example/endpoints/app/test/src/ExampleTests.scala index 1302d85..3314f24 100644 --- a/example/endpoints/app/test/src/ExampleTests.scala +++ b/example/endpoints/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Endpoints - test(Endpoints){ host => + test("Endpoints") - withServer(Endpoints){ host => requests.get(s"$host/echo/200").text() ==> "Echo 200" requests.get(s"$host/echo/200").statusCode ==> 200 requests.get(s"$host/echo/400").text() ==> "Echo 400" diff --git a/example/endpoints/build.sc b/example/endpoints/build.sc index c0e75a7..e4c2108 100644 --- a/example/endpoints/build.sc +++ b/example/endpoints/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/formJsonPost/app/src/FormJsonPost.scala b/example/formJsonPost/app/src/FormJsonPost.scala index 3714f39..b994ac1 100644 --- a/example/formJsonPost/app/src/FormJsonPost.scala +++ b/example/formJsonPost/app/src/FormJsonPost.scala @@ -1,7 +1,7 @@ package app 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 } diff --git a/example/formJsonPost/app/test/src/ExampleTests.scala b/example/formJsonPost/app/test/src/ExampleTests.scala index 148ebfd..4178497 100644 --- a/example/formJsonPost/app/test/src/ExampleTests.scala +++ b/example/formJsonPost/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,23 +17,23 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'FormJsonPost - test(FormJsonPost){ host => - requests.post(s"$host/json", data = """{"value1": true, "value2": [3]}""").text() ==> - "OK true List(3)" + test("FormJsonPost") - withServer(FormJsonPost){ host => + val response1 = requests.post(s"$host/json", data = """{"value1": true, "value2": [3]}""") + ujson.read(response1.text()) ==> ujson.Str("OK true List(3)") - requests.post( + val response2 = requests.post( s"$host/form", data = Seq("value1" -> "hello", "value2" -> "1", "value2" -> "2") - ).text() ==> - "OK FormValue(hello,null) List(1, 2)" + ) + response2.text() ==> "OK FormValue(hello,null) List(1, 2)" - val resp = requests.post( + val response3 = requests.post( s"$host/upload", data = requests.MultiPart( requests.MultiItem("image", "...", "my-best-image.txt") ) ) - resp.text() ==> "my-best-image.txt" + response3.text() ==> "my-best-image.txt" } } } diff --git a/example/formJsonPost/build.sc b/example/formJsonPost/build.sc index c0e75a7..e4c2108 100644 --- a/example/formJsonPost/build.sc +++ b/example/formJsonPost/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/httpMethods/app/test/src/ExampleTests.scala b/example/httpMethods/app/test/src/ExampleTests.scala index e14bcf5..30fa87f 100644 --- a/example/httpMethods/app/test/src/ExampleTests.scala +++ b/example/httpMethods/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'HttpMethods - test(HttpMethods){ host => + test("HttpMethods") - withServer(HttpMethods){ host => requests.post(s"$host/login").text() ==> "do_the_login" requests.get(s"$host/login").text() ==> "show_the_login_form" } diff --git a/example/httpMethods/build.sc b/example/httpMethods/build.sc index c0e75a7..e4c2108 100644 --- a/example/httpMethods/build.sc +++ b/example/httpMethods/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/minimalApplication/app/test/src/ExampleTests.scala b/example/minimalApplication/app/test/src/ExampleTests.scala index 8c8ecb2..1cda7a6 100644 --- a/example/minimalApplication/app/test/src/ExampleTests.scala +++ b/example/minimalApplication/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests { - 'MinimalApplication - test(MinimalApplication) { host => + test("MinimalApplication") - withServer(MinimalApplication) { host => val success = requests.get(host) success.text() ==> "Hello World!" diff --git a/example/minimalApplication/build.sc b/example/minimalApplication/build.sc index c0e75a7..e4c2108 100644 --- a/example/minimalApplication/build.sc +++ b/example/minimalApplication/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/minimalApplication2/app/test/src/ExampleTests.scala b/example/minimalApplication2/app/test/src/ExampleTests.scala index 0d8f1bc..4e5621c 100644 --- a/example/minimalApplication2/app/test/src/ExampleTests.scala +++ b/example/minimalApplication2/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'MinimalApplication2 - test(MinimalMain){ host => + test("MinimalApplication2") - withServer(MinimalMain){ host => val success = requests.get(host) success.text() ==> "Hello World!" diff --git a/example/minimalApplication2/build.sc b/example/minimalApplication2/build.sc index c0e75a7..e4c2108 100644 --- a/example/minimalApplication2/build.sc +++ b/example/minimalApplication2/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/redirectAbort/app/test/src/ExampleTests.scala b/example/redirectAbort/app/test/src/ExampleTests.scala index f095517..a4d149f 100644 --- a/example/redirectAbort/app/test/src/ExampleTests.scala +++ b/example/redirectAbort/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -18,7 +18,7 @@ object ExampleTests extends TestSuite{ val tests = Tests{ - 'RedirectAbort - test(RedirectAbort){ host => + test("RedirectAbort") - withServer(RedirectAbort){ host => val resp = requests.get(s"$host/") resp.statusCode ==> 401 resp.history.get.statusCode ==> 301 diff --git a/example/redirectAbort/build.sc b/example/redirectAbort/build.sc index c0e75a7..e4c2108 100644 --- a/example/redirectAbort/build.sc +++ b/example/redirectAbort/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/scalatags/app/test/src/ExampleTests.scala b/example/scalatags/app/test/src/ExampleTests.scala index 8eccecf..53bc1ea 100644 --- a/example/scalatags/app/test/src/ExampleTests.scala +++ b/example/scalatags/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests { - 'Scalatags - test(Scalatags) { host => + test("Scalatags") - withServer(Scalatags) { host => val body = requests.get(host).text() assert( diff --git a/example/scalatags/build.sc b/example/scalatags/build.sc index 3971d88..e95569a 100644 --- a/example/scalatags/build.sc +++ b/example/scalatags/build.sc @@ -11,7 +11,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/staticFiles/app/test/src/ExampleTests.scala b/example/staticFiles/app/test/src/ExampleTests.scala index ab67bcf..ebda8a0 100644 --- a/example/staticFiles/app/test/src/ExampleTests.scala +++ b/example/staticFiles/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -18,7 +18,7 @@ object ExampleTests extends TestSuite{ val tests = Tests{ - 'StaticFiles - test(StaticFiles){ host => + test("StaticFiles") - withServer(StaticFiles){ host => requests.get(s"$host/static/file/example.txt").text() ==> "the quick brown fox jumps over the lazy dog" diff --git a/example/staticFiles/build.sc b/example/staticFiles/build.sc index d45045d..e0f8519 100644 --- a/example/staticFiles/build.sc +++ b/example/staticFiles/build.sc @@ -11,7 +11,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) diff --git a/example/todo/app/src/TodoServer.scala b/example/todo/app/src/TodoServer.scala index 4adb55c..1a58d09 100644 --- a/example/todo/app/src/TodoServer.scala +++ b/example/todo/app/src/TodoServer.scala @@ -17,7 +17,7 @@ object TodoServer extends cask.MainRoutes{ class transactional extends cask.Decorator{ class TransactionFailed(val value: Router.Result.Error) extends Exception - def wrapFunction(pctx: cask.Request, delegate: Delegate): Returned = { + def wrapFunction(pctx: cask.Request, delegate: Delegate): OuterReturned = { try ctx.transaction( delegate(Map()) match{ case Router.Result.Success(t) => Router.Result.Success(t) diff --git a/example/todo/app/test/src/ExampleTests.scala b/example/todo/app/test/src/ExampleTests.scala index e8ca7eb..e1be23c 100644 --- a/example/todo/app/test/src/ExampleTests.scala +++ b/example/todo/app/test/src/ExampleTests.scala @@ -1,7 +1,7 @@ package app import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = io.undertow.Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -13,7 +13,7 @@ object ExampleTests extends TestSuite{ res } val tests = Tests{ - 'TodoServer - test(TodoServer){ host => + test("TodoServer") - withServer(TodoServer){ host => val page = requests.get(host).text() assert(page.contains("What needs to be done?")) } diff --git a/example/todo/build.sc b/example/todo/build.sc index beeb947..c5d5610 100644 --- a/example/todo/build.sc +++ b/example/todo/build.sc @@ -13,7 +13,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/todoApi/app/test/src/ExampleTests.scala b/example/todoApi/app/test/src/ExampleTests.scala index 5e9e11a..4e85a8e 100644 --- a/example/todoApi/app/test/src/ExampleTests.scala +++ b/example/todoApi/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'TodoMvcApi - test(TodoMvcApi){ host => + test("TodoMvcApi") - withServer(TodoMvcApi){ host => requests.get(s"$host/list/all").text() ==> """[{"checked":true,"text":"Get started with Cask"},{"checked":false,"text":"Profit!"}]""" requests.get(s"$host/list/active").text() ==> diff --git a/example/todoApi/build.sc b/example/todoApi/build.sc index c0e75a7..e4c2108 100644 --- a/example/todoApi/build.sc +++ b/example/todoApi/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/todoDb/app/src/TodoMvcDb.scala b/example/todoDb/app/src/TodoMvcDb.scala index 7ce3c50..669b2b8 100644 --- a/example/todoDb/app/src/TodoMvcDb.scala +++ b/example/todoDb/app/src/TodoMvcDb.scala @@ -16,7 +16,7 @@ object TodoMvcDb extends cask.MainRoutes{ class transactional extends cask.Decorator{ class TransactionFailed(val value: Router.Result.Error) extends Exception - def wrapFunction(pctx: cask.Request, delegate: Delegate): Returned = { + def wrapFunction(pctx: cask.Request, delegate: Delegate): OuterReturned = { try ctx.transaction( delegate(Map()) match{ case Router.Result.Success(t) => Router.Result.Success(t) diff --git a/example/todoDb/app/test/src/ExampleTests.scala b/example/todoDb/app/test/src/ExampleTests.scala index eccd913..77cbbb3 100644 --- a/example/todoDb/app/test/src/ExampleTests.scala +++ b/example/todoDb/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'TodoMvcDb - test(TodoMvcDb){ host => + test("TodoMvcDb") - withServer(TodoMvcDb){ host => requests.get(s"$host/list/all").text() ==> """[{"id":1,"checked":true,"text":"Get started with Cask"},{"id":2,"checked":false,"text":"Profit!"}]""" requests.get(s"$host/list/active").text() ==> diff --git a/example/todoDb/build.sc b/example/todoDb/build.sc index e2afdfc..589af39 100644 --- a/example/todoDb/build.sc +++ b/example/todoDb/build.sc @@ -12,7 +12,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/twirl/app/test/src/ExampleTests.scala b/example/twirl/app/test/src/ExampleTests.scala index 815f656..2c445fb 100644 --- a/example/twirl/app/test/src/ExampleTests.scala +++ b/example/twirl/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests { - 'Twirl - test(Twirl) { host => + test("Twirl") - withServer(Twirl) { host => val body = requests.get(host).text() assert( diff --git a/example/twirl/build.sc b/example/twirl/build.sc index 6a59f0d..de9fc20 100644 --- a/example/twirl/build.sc +++ b/example/twirl/build.sc @@ -15,7 +15,7 @@ trait AppModule extends ScalaModule with mill.twirllib.TwirlModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/variableRoutes/app/test/src/ExampleTests.scala b/example/variableRoutes/app/test/src/ExampleTests.scala index 48455fa..1755dea 100644 --- a/example/variableRoutes/app/test/src/ExampleTests.scala +++ b/example/variableRoutes/app/test/src/ExampleTests.scala @@ -4,7 +4,7 @@ import io.undertow.Undertow import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -17,7 +17,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'VariableRoutes - test(VariableRoutes){ host => + test("VariableRoutes") - withServer(VariableRoutes){ host => val noIndexPage = requests.get(host) noIndexPage.statusCode ==> 404 diff --git a/example/variableRoutes/build.sc b/example/variableRoutes/build.sc index c0e75a7..e4c2108 100644 --- a/example/variableRoutes/build.sc +++ b/example/variableRoutes/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ) } diff --git a/example/websockets/app/test/src/ExampleTests.scala b/example/websockets/app/test/src/ExampleTests.scala index 896c051..a463824 100644 --- a/example/websockets/app/test/src/ExampleTests.scala +++ b/example/websockets/app/test/src/ExampleTests.scala @@ -8,7 +8,7 @@ import utest._ object ExampleTests extends TestSuite{ - def test[T](example: cask.main.BaseMain)(f: String => T): T = { + def withServer[T](example: cask.main.BaseMain)(f: String => T): T = { val server = io.undertow.Undertow.builder .addHttpListener(8080, "localhost") .setHandler(example.defaultHandler) @@ -21,7 +21,7 @@ object ExampleTests extends TestSuite{ } val tests = Tests{ - 'Websockets - test(Websockets){ host => + test("Websockets") - withServer(Websockets){ host => @volatile var out = List.empty[String] val client = org.asynchttpclient.Dsl.asyncHttpClient(); try{ @@ -72,7 +72,7 @@ object ExampleTests extends TestSuite{ } } - 'Websockets2000 - test(Websockets){ host => + test("Websockets2000") - withServer(Websockets){ host => @volatile var out = List.empty[String] val closed = new AtomicInteger(0) val client = org.asynchttpclient.Dsl.asyncHttpClient(); diff --git a/example/websockets/build.sc b/example/websockets/build.sc index c788216..197e285 100644 --- a/example/websockets/build.sc +++ b/example/websockets/build.sc @@ -9,7 +9,7 @@ trait AppModule extends ScalaModule{ def testFrameworks = Seq("utest.runner.Framework") def ivyDeps = Agg( - ivy"com.lihaoyi::utest::0.6.9", + ivy"com.lihaoyi::utest::0.7.1", ivy"com.lihaoyi::requests::0.2.0", ivy"org.asynchttpclient:async-http-client:2.5.2" ) |