summaryrefslogtreecommitdiff
path: root/cask/src/cask/main
diff options
context:
space:
mode:
Diffstat (limited to 'cask/src/cask/main')
-rw-r--r--cask/src/cask/main/Decorators.scala136
-rw-r--r--cask/src/cask/main/ErrorMsgs.scala21
-rw-r--r--cask/src/cask/main/Main.scala24
-rw-r--r--cask/src/cask/main/Routes.scala4
-rw-r--r--cask/src/cask/main/RoutesEndpointMetadata.scala68
5 files changed, 26 insertions, 227 deletions
diff --git a/cask/src/cask/main/Decorators.scala b/cask/src/cask/main/Decorators.scala
deleted file mode 100644
index fb795ba..0000000
--- a/cask/src/cask/main/Decorators.scala
+++ /dev/null
@@ -1,136 +0,0 @@
-package cask.main
-
-import cask.internal.{Conversion, Router}
-import cask.internal.Router.{ArgReader, EntryPoint}
-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
-}
-object Decorator{
- /**
- * A stack of [[Decorator]]s is invoked recursively: each decorator's `wrapFunction`
- * is invoked around the invocation of all inner decorators, with the inner-most
- * decorator finally invoking the route's [[EntryPoint.invoke]] function.
- *
- * Each decorator (and the final `Endpoint`) contributes a dictionary of name-value
- * bindings, which are eventually all passed to [[EntryPoint.invoke]]. Each decorator's
- * dictionary corresponds to a different argument list on [[EntryPoint.invoke]]. The
- * bindings passed from the router are aggregated with those from the `EndPoint` and
- * used as the first argument list.
- */
- def invoke[T](ctx: Request,
- endpoint: Endpoint[_, _],
- entryPoint: EntryPoint[T, _],
- routes: T,
- routeBindings: Map[String, String],
- remainingDecorators: List[RawDecorator],
- bindings: List[Map[String, Any]]): Router.Result[Any] = try {
- remainingDecorators match {
- case head :: rest =>
- head.wrapFunction(
- ctx,
- args => invoke(ctx, endpoint, entryPoint, routes, routeBindings, rest, args :: bindings)
- .asInstanceOf[Router.Result[cask.model.Response.Raw]]
- )
-
- case Nil =>
- endpoint.wrapFunction(ctx, { (endpointBindings: Map[String, Any]) =>
- val mergedEndpointBindings = endpointBindings ++ routeBindings.mapValues(endpoint.wrapPathSegment)
- val finalBindings = mergedEndpointBindings :: bindings
-
- entryPoint
- .asInstanceOf[EntryPoint[T, cask.model.Request]]
- .invoke(routes, ctx, finalBindings)
- .asInstanceOf[Router.Result[Nothing]]
- })
- }
- // Make sure we wrap any exceptions that bubble up from decorator
- // bodies, so outer decorators do not need to worry about their
- // delegate throwing on them
- }catch{case e: Throwable => Router.Result.Error.Exception(e) }
-}
-
-/**
- * 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]
-}
-
-
-/**
- * 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 Endpoint[InnerReturned, Input] extends Decorator[InnerReturned, Input]{
- /**
- * What is the path that this particular endpoint matches?
- */
- val path: String
- /**
- * Which HTTP methods does this endpoint support? POST? GET? PUT? Or some
- * combination of those?
- */
- val methods: Seq[String]
-
- /**
- * Whether or not this endpoint allows matching on sub-paths: does
- * `@endpoint("/foo")` capture the path "/foo/bar/baz"? Useful to e.g. have
- * an endpoint match URLs with paths in a filesystem (real or virtual) to
- * serve files
- */
- def subpath: Boolean = false
-
- def convertToResultType[T](t: T)
- (implicit f: Conversion[T, InnerReturned]): InnerReturned = {
- f.f(t)
- }
-
- /**
- * [[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
- * instance of the appropriate type.
- */
- def wrapPathSegment(s: String): Input
-
-}
-
-/**
- * Annotates a Cask endpoint that returns a HTTP [[Response]]; similar to a
- * [[RawDecorator]] but with additional metadata and capabilities.
- */
-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
-
- def read(ctx: Request, label: String, input: Input) = input.asInstanceOf[T]
-}
-object NoOpParser{
- implicit def instance[Input, T] = new NoOpParser[Input, T]
- implicit def instanceAny[T] = new NoOpParser[Any, T]
-} \ No newline at end of file
diff --git a/cask/src/cask/main/ErrorMsgs.scala b/cask/src/cask/main/ErrorMsgs.scala
index 254f4e0..a22bd89 100644
--- a/cask/src/cask/main/ErrorMsgs.scala
+++ b/cask/src/cask/main/ErrorMsgs.scala
@@ -1,11 +1,12 @@
package cask.main
-import cask.internal.{Router, Util}
+import cask.internal.Util
import cask.internal.Util.literalize
+import cask.router.{ArgSig, EntryPoint, Result}
object ErrorMsgs {
- def getLeftColWidth(items: Seq[Router.ArgSig[_, _, _,_]]) = {
+ def getLeftColWidth(items: Seq[ArgSig[_, _, _,_]]) = {
items.map(_.name.length + 2) match{
case Nil => 0
case x => x.max
@@ -13,7 +14,7 @@ object ErrorMsgs {
}
def renderArg[T](base: T,
- arg: Router.ArgSig[_, T, _, _],
+ arg: ArgSig[_, T, _, _],
leftOffset: Int,
wrappedWidth: Int): (String, String) = {
val suffix = arg.default match{
@@ -33,7 +34,7 @@ object ErrorMsgs {
}
def formatMainMethodSignature[T](base: T,
- main: Router.EntryPoint[T, _],
+ main: EntryPoint[T, _],
leftIndent: Int,
leftColWidth: Int) = {
// +2 for space on right of left col
@@ -56,12 +57,12 @@ object ErrorMsgs {
|${argStrings.map(_ + "\n").mkString}""".stripMargin
}
- def formatInvokeError[T](base: T, route: Router.EntryPoint[T, _], x: Router.Result.Error): String = {
+ def formatInvokeError[T](base: T, route: EntryPoint[T, _], x: Result.Error): String = {
def expectedMsg = formatMainMethodSignature(base: T, route, 0, 0)
x match{
- case Router.Result.Error.Exception(x) => Util.stackTraceString(x)
- case Router.Result.Error.MismatchedArguments(missing, unknown) =>
+ case Result.Error.Exception(x) => Util.stackTraceString(x)
+ case Result.Error.MismatchedArguments(missing, unknown) =>
val missingStr =
if (missing.isEmpty) ""
else {
@@ -88,14 +89,14 @@ object ErrorMsgs {
|$expectedMsg
|""".stripMargin
- case Router.Result.Error.InvalidArguments(x) =>
+ case Result.Error.InvalidArguments(x) =>
val argumentsStr = Util.pluralize("argument", x.length)
val thingies = x.map{
- case Router.Result.ParamError.Invalid(p, v, ex) =>
+ case Result.ParamError.Invalid(p, v, ex) =>
val literalV = literalize(v)
val trace = Util.stackTraceString(ex)
s"${p.name}: ${p.typeString} = $literalV failed to parse with $ex\n$trace"
- case Router.Result.ParamError.DefaultFailed(p, ex) =>
+ case Result.ParamError.DefaultFailed(p, ex) =>
val trace = Util.stackTraceString(ex)
s"${p.name}'s default value failed to evaluate with $ex\n$trace"
}
diff --git a/cask/src/cask/main/Main.scala b/cask/src/cask/main/Main.scala
index fddd9b7..6d08c04 100644
--- a/cask/src/cask/main/Main.scala
+++ b/cask/src/cask/main/Main.scala
@@ -2,9 +2,9 @@ package cask.main
import cask.endpoints.{WebsocketResult, WsHandler}
import cask.model._
-import cask.internal.Router.EntryPoint
-import cask.internal.{DispatchTrie, Router, Util}
+import cask.internal.{DispatchTrie, Util}
import cask.main
+import cask.router.{Decorator, EndpointMetadata, EntryPoint, RawDecorator, Result}
import cask.util.Logger
import io.undertow.Undertow
import io.undertow.server.{HttpHandler, HttpServerExchange}
@@ -27,7 +27,7 @@ class MainRoutes extends Main with Routes{
* application-wide properties.
*/
abstract class Main{
- def mainDecorators: Seq[cask.main.RawDecorator] = Nil
+ def mainDecorators: Seq[RawDecorator] = Nil
def allRoutes: Seq[Routes]
def port: Int = 8080
def host: String = "localhost"
@@ -45,7 +45,7 @@ abstract class Main{
def handleEndpointError(routes: Routes,
metadata: EndpointMetadata[_],
- e: Router.Result.Error) = {
+ e: cask.router.Result.Error) = {
Main.defaultHandleError(routes, metadata, e, debugMode)
}
@@ -64,7 +64,7 @@ object Main{
mainDecorators: Seq[RawDecorator],
debugMode: Boolean,
handleNotFound: () => Response.Raw,
- handleError: (Routes, EndpointMetadata[_], Router.Result.Error) => Response.Raw)
+ handleError: (Routes, EndpointMetadata[_], Result.Error) => Response.Raw)
(implicit log: Logger) extends HttpHandler() {
def handleRequest(exchange: HttpServerExchange): Unit = try {
// println("Handling Request: " + exchange.getRequestPath)
@@ -98,8 +98,8 @@ object Main{
(mainDecorators ++ routes.decorators ++ metadata.decorators).toList,
Nil
) match{
- case Router.Result.Success(res) => runner(res)
- case e: Router.Result.Error =>
+ case Result.Success(res) => runner(res)
+ case e: Result.Error =>
Main.writeResponse(
exchange,
handleError(routes, metadata, e)
@@ -145,17 +145,17 @@ object Main{
def defaultHandleError(routes: Routes,
metadata: EndpointMetadata[_],
- e: Router.Result.Error,
+ e: Result.Error,
debugMode: Boolean)
(implicit log: Logger) = {
e match {
- case e: Router.Result.Error.Exception => log.exception(e.t)
+ case e: Result.Error.Exception => log.exception(e.t)
case _ => // do nothing
}
val statusCode = e match {
- case _: Router.Result.Error.Exception => 500
- case _: Router.Result.Error.InvalidArguments => 400
- case _: Router.Result.Error.MismatchedArguments => 400
+ case _: Result.Error.Exception => 500
+ case _: Result.Error.InvalidArguments => 400
+ case _: Result.Error.MismatchedArguments => 400
}
val str =
diff --git a/cask/src/cask/main/Routes.scala b/cask/src/cask/main/Routes.scala
index 9be9f50..512860a 100644
--- a/cask/src/cask/main/Routes.scala
+++ b/cask/src/cask/main/Routes.scala
@@ -1,10 +1,12 @@
package cask.main
+import cask.router.RoutesEndpointsMetadata
+
import language.experimental.macros
trait Routes{
- def decorators = Seq.empty[cask.main.RawDecorator]
+ def decorators = Seq.empty[cask.router.RawDecorator]
private[this] var metadata0: RoutesEndpointsMetadata[this.type] = null
def caskMetadata =
if (metadata0 != null) metadata0
diff --git a/cask/src/cask/main/RoutesEndpointMetadata.scala b/cask/src/cask/main/RoutesEndpointMetadata.scala
deleted file mode 100644
index fa93a0c..0000000
--- a/cask/src/cask/main/RoutesEndpointMetadata.scala
+++ /dev/null
@@ -1,68 +0,0 @@
-package cask.main
-
-import cask.internal.Router.EntryPoint
-
-import language.experimental.macros
-import scala.reflect.macros.blackbox
-case class EndpointMetadata[T](decorators: Seq[RawDecorator],
- endpoint: Endpoint[_, _],
- entryPoint: EntryPoint[T, _])
-case class RoutesEndpointsMetadata[T](value: EndpointMetadata[T]*)
-object RoutesEndpointsMetadata{
- implicit def initialize[T]: RoutesEndpointsMetadata[T] = macro initializeImpl[T]
- implicit def initializeImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[RoutesEndpointsMetadata[T]] = {
- import c.universe._
- val router = new cask.internal.Router[c.type](c)
-
- val routeParts = for{
- m <- c.weakTypeOf[T].members
- annotations = m.annotations.filter(_.tree.tpe <:< c.weakTypeOf[Decorator[_, _]])
- if annotations.nonEmpty
- } yield {
- if(!(annotations.last.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.last.tree.tpe}"
- )
- val allEndpoints = annotations.filter(_.tree.tpe <:< weakTypeOf[Endpoint[_, _]])
- if(allEndpoints.length > 1) c.abort(
- annotations.last.tree.pos,
- s"You can only apply one Endpoint annotation to a function, not " +
- s"${allEndpoints.length} in ${allEndpoints.map(_.tree.tpe).mkString(", ")}"
- )
-
- val annotObjects =
- for(annot <- annotations)
- yield q"new ${annot.tree.tpe}(..${annot.tree.children.tail})"
-
- val annotObjectSyms =
- for(_ <- annotations.indices)
- yield c.universe.TermName(c.freshName("annotObject"))
-
- val route = router.extractMethod(
- m.asInstanceOf[MethodSymbol],
- weakTypeOf[T],
- q"${annotObjectSyms.last}.convertToResultType",
- tq"cask.Request",
- annotObjectSyms.reverse.map(annotObjectSym => q"$annotObjectSym.getParamParser"),
- annotObjectSyms.reverse.map(annotObjectSym => tq"$annotObjectSym.InputTypeAlias")
- )
-
- val declarations =
- for((sym, obj) <- annotObjectSyms.zip(annotObjects))
- yield q"val $sym = $obj"
-
- val res = q"""{
- ..$declarations
- cask.main.EndpointMetadata(
- Seq(..${annotObjectSyms.dropRight(1)}),
- ${annotObjectSyms.last},
- $route
- )
- }"""
- res
- }
-
- c.Expr[RoutesEndpointsMetadata[T]](q"""cask.main.RoutesEndpointsMetadata(..$routeParts)""")
- }
-} \ No newline at end of file