diff options
Diffstat (limited to 'cask/src/cask/main')
-rw-r--r-- | cask/src/cask/main/Decorators.scala | 136 | ||||
-rw-r--r-- | cask/src/cask/main/ErrorMsgs.scala | 21 | ||||
-rw-r--r-- | cask/src/cask/main/Main.scala | 24 | ||||
-rw-r--r-- | cask/src/cask/main/Routes.scala | 4 | ||||
-rw-r--r-- | cask/src/cask/main/RoutesEndpointMetadata.scala | 68 |
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 |