summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-07-23 21:49:11 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-07-23 21:57:01 +0800
commit603dfa8946f8c78580568613cd268ad05c6c38f6 (patch)
tree6be83f40a79aa58517f8903e49d0d978c7372f9d
parent3fcab5dc0dc5df56976d87a7097549a55ec8e321 (diff)
downloadcask-603dfa8946f8c78580568613cd268ad05c6c38f6.tar.gz
cask-603dfa8946f8c78580568613cd268ad05c6c38f6.tar.bz2
cask-603dfa8946f8c78580568613cd268ad05c6c38f6.zip
First pass at specializing `ParamReader` for `JsonReader` and `FormReader`
-rw-r--r--build.sc3
-rw-r--r--cask/src/cask/Endpoints.scala31
-rw-r--r--cask/src/cask/FormEndpoint.scala58
-rw-r--r--cask/src/cask/FormValue.scala23
-rw-r--r--cask/src/cask/JsonEndpoint.scala43
-rw-r--r--cask/src/cask/Main.scala6
-rw-r--r--cask/src/cask/QueryParamReader.scala (renamed from cask/src/cask/ParamReader.scala)48
-rw-r--r--cask/src/cask/Router.scala153
-rw-r--r--cask/src/cask/Routes.scala33
-rw-r--r--cask/src/cask/Status.scala1
-rw-r--r--cask/test/src/test/cask/FormJsonPost.scala19
11 files changed, 265 insertions, 153 deletions
diff --git a/build.sc b/build.sc
index 7c3c1ba..0862ad9 100644
--- a/build.sc
+++ b/build.sc
@@ -5,7 +5,8 @@ object cask extends ScalaModule{
def ivyDeps = Agg(
ivy"org.scala-lang:scala-reflect:$scalaVersion",
ivy"io.undertow:undertow-core:2.0.11.Final",
- ivy"com.github.scopt::scopt:3.5.0"
+ ivy"com.github.scopt::scopt:3.5.0",
+ ivy"com.lihaoyi::upickle:0.6.6"
)
object test extends Tests{
def forkArgs = Seq("--illegal-access=deny")
diff --git a/cask/src/cask/Endpoints.scala b/cask/src/cask/Endpoints.scala
index 8363104..b0bc207 100644
--- a/cask/src/cask/Endpoints.scala
+++ b/cask/src/cask/Endpoints.scala
@@ -2,35 +2,37 @@ package cask
import cask.Router.EntryPoint
import io.undertow.server.HttpServerExchange
+
import collection.JavaConverters._
trait Endpoint[R]{
+ type InputType
val path: String
def subpath: Boolean = false
def wrapMethodOutput(t: R): Any
- def parseMethodInput[T](implicit p: ParamReader[T]) = p
-
def handle(exchange: HttpServerExchange,
remaining: Seq[String],
bindings: Map[String, String],
routes: Routes,
- entryPoint: EntryPoint[Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response]
+ entryPoint: EntryPoint[InputType, Routes, (HttpServerExchange, Seq[String])]): Router.Result[BaseResponse]
}
trait WebEndpoint extends Endpoint[Response]{
+ type InputType = Seq[String]
def wrapMethodOutput(t: Response) = t
+ def parseMethodInput[T](implicit p: QueryParamReader[T]) = p
def handle(exchange: HttpServerExchange,
remaining: Seq[String],
bindings: Map[String, String],
routes: Routes,
- entryPoint: EntryPoint[Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
+ entryPoint: EntryPoint[Seq[String], Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
val allBindings =
- bindings.toSeq ++
- exchange.getQueryParameters
- .asScala
- .toSeq
- .flatMap{case (k, vs) => vs.asScala.map((k, _))}
+ bindings.map{case (k, v) => (k, Seq(v))} ++
+ exchange.getQueryParameters
+ .asScala
+ .toSeq
+ .map{case (k, vs) => (k, vs.asScala.toSeq)}
- entryPoint.invoke(routes, (exchange, remaining), allBindings.map{case (k, v) => (k, Some(v))})
+ entryPoint.invoke(routes, (exchange, remaining), allBindings)
.asInstanceOf[Router.Result[Response]]
}
}
@@ -39,17 +41,18 @@ class post(val path: String, override val subpath: Boolean = false) extends WebE
class put(val path: String, override val subpath: Boolean = false) extends WebEndpoint
class route(val path: String, val methods: Seq[String], override val subpath: Boolean = false) extends WebEndpoint
class static(val path: String) extends Endpoint[String] {
+ type InputType = Seq[String]
override def subpath = true
def wrapOutput(t: String) = t
-
+ def parseMethodInput[T](implicit p: QueryParamReader[T]) = p
def wrapMethodOutput(t: String) = t
def handle(exchange: HttpServerExchange,
remaining: Seq[String],
bindings: Map[String, String],
routes: Routes,
- entryPoint: EntryPoint[Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
- entryPoint.invoke(routes, (exchange, remaining), Nil).asInstanceOf[Router.Result[String]] match{
+ entryPoint: EntryPoint[Seq[String], Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
+ entryPoint.invoke(routes, (exchange, remaining), Map()).asInstanceOf[Router.Result[String]] match{
case Router.Result.Success(s) =>
val relPath = java.nio.file.Paths.get(
s + "/" + remaining.mkString("/")
@@ -65,4 +68,4 @@ class static(val path: String) extends Endpoint[String] {
}
}
-}
+} \ No newline at end of file
diff --git a/cask/src/cask/FormEndpoint.scala b/cask/src/cask/FormEndpoint.scala
new file mode 100644
index 0000000..0d6f2f2
--- /dev/null
+++ b/cask/src/cask/FormEndpoint.scala
@@ -0,0 +1,58 @@
+package cask
+
+import cask.Router.EntryPoint
+import io.undertow.server.HttpServerExchange
+import io.undertow.server.handlers.form.FormParserFactory
+import collection.JavaConverters._
+
+sealed trait FormReader[T] extends Router.ArgReader[Seq[FormValue], T, (HttpServerExchange, Seq[String])]
+object FormReader{
+ implicit def paramFormReader[T: QueryParamReader] = new FormReader[T]{
+ def arity = implicitly[QueryParamReader[T]].arity
+
+ def read(ctx: (HttpServerExchange, Seq[String]), input: Seq[FormValue]) = {
+ implicitly[QueryParamReader[T]].read(ctx, input.map(_.value))
+ }
+ }
+ implicit def formValueReader = new FormReader[FormValue]{
+ def arity = 1
+ def read(ctx: (HttpServerExchange, Seq[String]), input: Seq[FormValue]) = input.head
+ }
+ implicit def formValuesReader = new FormReader[Seq[FormValue]]{
+ def arity = 1
+ def read(ctx: (HttpServerExchange, Seq[String]), input: Seq[FormValue]) = input
+ }
+ implicit def formValueFileReader = new FormReader[FormValue.File]{
+ def arity = 1
+ def read(ctx: (HttpServerExchange, Seq[String]), input: Seq[FormValue]) = input.head.asFile.get
+ }
+ implicit def formValuesFileReader = new FormReader[Seq[FormValue.File]]{
+ def arity = 1
+ def read(ctx: (HttpServerExchange, Seq[String]), input: Seq[FormValue]) = input.map(_.asFile.get)
+ }
+}
+class postForm(val path: String, override val subpath: Boolean = false) extends Endpoint[Response]{
+ type InputType = Seq[FormValue]
+ def wrapMethodOutput(t: Response) = t
+ def parseMethodInput[T](implicit p: FormReader[T]) = p
+ def handle(exchange: HttpServerExchange,
+ remaining: Seq[String],
+ bindings: Map[String, String],
+ routes: Routes,
+ entryPoint: EntryPoint[Seq[FormValue], Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
+
+ val formData = FormParserFactory.builder().build().createParser(exchange).parseBlocking()
+ val formDataBindings =
+ formData
+ .iterator()
+ .asScala
+ .map(k => (k, formData.get(k).asScala.map(FormValue.fromUndertow).toSeq))
+
+
+ val pathBindings =
+ bindings.map{case (k, v) => (k, Seq(new FormValue.Plain(v, new io.undertow.util.HeaderMap())))}
+
+ entryPoint.invoke(routes, (exchange, remaining), pathBindings ++ formDataBindings)
+ .asInstanceOf[Router.Result[Response]]
+ }
+}
diff --git a/cask/src/cask/FormValue.scala b/cask/src/cask/FormValue.scala
new file mode 100644
index 0000000..60a2d39
--- /dev/null
+++ b/cask/src/cask/FormValue.scala
@@ -0,0 +1,23 @@
+package cask
+
+object FormValue{
+ def fromUndertow(from: io.undertow.server.handlers.form.FormData.FormValue) = {
+ if (!from.isFile) Plain(from.getValue, from.getHeaders)
+ else File(from.getValue, from.getFileName, from.getPath, from.getHeaders)
+ }
+ case class Plain(value: String,
+ headers: io.undertow.util.HeaderMap) extends FormValue
+
+ case class File(value: String,
+ fileName: String,
+ filePath: java.nio.file.Path,
+ headers: io.undertow.util.HeaderMap) extends FormValue
+}
+sealed trait FormValue{
+ def value: String
+ def headers: io.undertow.util.HeaderMap
+ def asFile: Option[FormValue.File] = this match{
+ case p: FormValue.Plain => None
+ case p: FormValue.File => Some(p)
+ }
+}
diff --git a/cask/src/cask/JsonEndpoint.scala b/cask/src/cask/JsonEndpoint.scala
new file mode 100644
index 0000000..58fd596
--- /dev/null
+++ b/cask/src/cask/JsonEndpoint.scala
@@ -0,0 +1,43 @@
+package cask
+
+import cask.Router.EntryPoint
+import io.undertow.server.HttpServerExchange
+
+
+sealed trait JsReader[T] extends Router.ArgReader[ujson.Js.Value, T, (HttpServerExchange, Seq[String])]
+object JsReader{
+ implicit def defaultJsReader[T: upickle.default.Reader] = new JsReader[T]{
+ def arity = 1
+
+ def read(ctx: (HttpServerExchange, Seq[String]), input: ujson.Js.Value): T = {
+ implicitly[upickle.default.Reader[T]].apply(input)
+ }
+ }
+
+ implicit def paramReader[T: ParamReader] = new JsReader[T] {
+ override def arity = 0
+
+ override def read(ctx: (HttpServerExchange, Seq[String]), v: ujson.Js.Value) = {
+ implicitly[ParamReader[T]].read(ctx, Nil)
+ }
+ }
+}
+class postJson(val path: String, override val subpath: Boolean = false) extends Endpoint[Response]{
+ type InputType = ujson.Js.Value
+ def wrapMethodOutput(t: Response) = t
+ def parseMethodInput[T](implicit p: JsReader[T]) = p
+ def handle(exchange: HttpServerExchange,
+ remaining: Seq[String],
+ bindings: Map[String, String],
+ routes: Routes,
+ entryPoint: EntryPoint[ujson.Js.Value, Routes, (HttpServerExchange, Seq[String])]): Router.Result[Response] = {
+
+ val js = ujson.read(new String(exchange.getInputStream.readAllBytes())).asInstanceOf[ujson.Js.Obj]
+
+ js.obj
+ val allBindings = bindings.mapValues(ujson.Js.Str(_))
+
+ entryPoint.invoke(routes, (exchange, remaining), js.obj.toMap ++ allBindings)
+ .asInstanceOf[Router.Result[Response]]
+ }
+}
diff --git a/cask/src/cask/Main.scala b/cask/src/cask/Main.scala
index 6ee928e..188c24c 100644
--- a/cask/src/cask/Main.scala
+++ b/cask/src/cask/Main.scala
@@ -37,7 +37,7 @@ abstract class BaseMain{
)
}
- def writeResponse(exchange: HttpServerExchange, response: Response) = {
+ def writeResponse(exchange: HttpServerExchange, response: BaseResponse) = {
response.headers.foreach{case (k, v) =>
exchange.getResponseHeaders.put(new HttpString(k), v)
}
@@ -54,11 +54,11 @@ abstract class BaseMain{
case Some(((routes, metadata), bindings, remaining)) =>
val result = metadata.metadata.handle(
exchange, remaining, bindings, routes,
- metadata.entryPoint.asInstanceOf[EntryPoint[Routes, (HttpServerExchange, Seq[String])]]
+ metadata.entryPoint.asInstanceOf[EntryPoint[metadata.metadata.InputType, cask.Routes, (HttpServerExchange, Seq[String])]]
)
result match{
- case Router.Result.Success(response: Response) => writeResponse(exchange, response)
+ case Router.Result.Success(response) => writeResponse(exchange, response)
case Router.Result.Error.Exception(e) =>
println(e)
e.printStackTrace()
diff --git a/cask/src/cask/ParamReader.scala b/cask/src/cask/QueryParamReader.scala
index ac3614f..1d34b14 100644
--- a/cask/src/cask/ParamReader.scala
+++ b/cask/src/cask/QueryParamReader.scala
@@ -3,20 +3,38 @@ package cask
import io.undertow.server.HttpServerExchange
import io.undertow.server.handlers.form.{FormData, FormParserFactory}
+
abstract class ParamReader[T]
- extends Router.ArgReader[T, (HttpServerExchange, Seq[String])]{
+ extends Router.ArgReader[Seq[String], T, (HttpServerExchange, Seq[String])]{
def arity: Int
def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T
}
object ParamReader{
- class SimpleParam[T](f: String => T) extends ParamReader[T]{
- def arity = 1
- def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T = f(v.head)
- }
class NilParam[T](f: (HttpServerExchange, Seq[String]) => T) extends ParamReader[T]{
def arity = 0
def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T = f(ctx._1, ctx._2)
}
+ implicit object HttpExchangeParam extends NilParam[HttpServerExchange]((server, remaining) => server)
+ implicit object SubpathParam extends NilParam[Subpath]((server, remaining) => new Subpath(remaining))
+ implicit object CookieParam extends NilParam[Cookies]((server, remaining) => {
+ import collection.JavaConverters._
+ new Cookies(server.getRequestCookies.asScala.toMap.map{case (k, v) => (k, Cookie.fromUndertow(v))})
+ })
+ implicit object FormDataParam extends NilParam[FormData]((server, remaining) =>
+ FormParserFactory.builder().build().createParser(server).parseBlocking()
+ )
+}
+abstract class QueryParamReader[T]
+ extends Router.ArgReader[Seq[String], T, (HttpServerExchange, Seq[String])]{
+ def arity: Int
+ def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T
+}
+object QueryParamReader{
+ class SimpleParam[T](f: String => T) extends QueryParamReader[T]{
+ def arity = 1
+ def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): T = f(v.head)
+ }
+
implicit object StringParam extends SimpleParam[String](x => x)
implicit object BooleanParam extends SimpleParam[Boolean](_.toBoolean)
implicit object ByteParam extends SimpleParam[Byte](_.toByte)
@@ -25,22 +43,20 @@ object ParamReader{
implicit object LongParam extends SimpleParam[Long](_.toLong)
implicit object DoubleParam extends SimpleParam[Double](_.toDouble)
implicit object FloatParam extends SimpleParam[Float](_.toFloat)
- implicit def SeqParam[T: ParamReader] = new ParamReader[Seq[T]]{
+ implicit def SeqParam[T: QueryParamReader] = new QueryParamReader[Seq[T]]{
def arity = 1
def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]): Seq[T] = {
- v.map(x => implicitly[ParamReader[T]].read(ctx, Seq(x)))
+ v.map(x => implicitly[QueryParamReader[T]].read(ctx, Seq(x)))
+ }
+ }
+ implicit def paramReader[T: ParamReader] = new QueryParamReader[T] {
+ override def arity = 0
+
+ override def read(ctx: (HttpServerExchange, Seq[String]), v: Seq[String]) = {
+ implicitly[ParamReader[T]].read(ctx, v)
}
}
- implicit object HttpExchangeParam extends NilParam[HttpServerExchange]((server, remaining) => server)
- implicit object SubpathParam extends NilParam[Subpath]((server, remaining) => new Subpath(remaining))
- implicit object CookieParam extends NilParam[Cookies]((server, remaining) => {
- import collection.JavaConverters._
- new Cookies(server.getRequestCookies.asScala.toMap.map{case (k, v) => (k, Cookie.fromUndertow(v))})
- })
- implicit object FormDataParam extends NilParam[FormData]((server, remaining) =>
- FormParserFactory.builder().build().createParser(server).parseBlocking()
- )
}
class Subpath(val value: Seq[String])
diff --git a/cask/src/cask/Router.scala b/cask/src/cask/Router.scala
index 649160e..90d5245 100644
--- a/cask/src/cask/Router.scala
+++ b/cask/src/cask/Router.scala
@@ -24,17 +24,15 @@ object Router{
* (just for logging and reading, not a replacement for a `TypeTag`) and
* possible a function that can compute its default value
*/
- case class ArgSig[-T, +V, -C](name: String,
- typeString: String,
- doc: Option[String],
- default: Option[T => V])
- (implicit val reads: ArgReader[V, C])
+ case class ArgSig[I, -T, +V, -C](name: String,
+ typeString: String,
+ doc: Option[String],
+ default: Option[T => V])
+ (implicit val reads: ArgReader[I, V, C])
- type AnyArgReader = ArgReader[Any, Nothing]
-
- trait ArgReader[+T, -C]{
+ trait ArgReader[I, +T, -C]{
def arity: Int
- def read(ctx: C, input: Seq[String]): T
+ def read(ctx: C, input: I): T
}
def stripDashes(s: String) = {
@@ -52,87 +50,14 @@ object Router{
* instead, which provides a nicer API to call it that mimmicks the API of
* calling a Scala method.
*/
- case class EntryPoint[T, C](name: String,
- argSignatures: Seq[ArgSig[T, _, C]],
- doc: Option[String],
- varargs: Boolean,
- invoke0: (T, C, Map[String, Seq[String]], Seq[String]) => Result[Any]){
- def invoke(target: T, ctx: C, groupedArgs: Seq[(String, Option[String])]): Result[Any] = {
- var remainingArgSignatures = argSignatures.toList.filter(_.reads.arity > 0)
-
- val accumulatedKeywords = mutable.Map.empty[ArgSig[T, _, C], mutable.Buffer[String]]
- val keywordableArgs = if (varargs) argSignatures.dropRight(1) else argSignatures
-
- for(arg <- keywordableArgs) accumulatedKeywords(arg) = mutable.Buffer.empty
-
- val leftoverArgs = mutable.Buffer.empty[String]
-
- val lookupArgSig = Map(argSignatures.map(x => (x.name, x)):_*)
-
- var incomplete: Option[ArgSig[T, _, C]] = None
-
- for(group <- groupedArgs){
-
- group match{
- case (value, None) =>
- if (value(0) == '-' && !varargs){
- lookupArgSig.get(stripDashes(value)) match{
- case None => leftoverArgs.append(value)
- case Some(sig) => incomplete = Some(sig)
- }
-
- } else remainingArgSignatures match {
- case Nil => leftoverArgs.append(value)
- case last :: Nil if varargs => leftoverArgs.append(value)
- case next :: rest =>
- accumulatedKeywords(next).append(value)
- remainingArgSignatures = rest
- }
- case (rawKey, Some(value)) =>
- val key = stripDashes(rawKey)
- lookupArgSig.get(key) match{
- case Some(x) if accumulatedKeywords.contains(x) =>
- if (accumulatedKeywords(x).nonEmpty && varargs){
- leftoverArgs.append(rawKey, value)
- }else{
- accumulatedKeywords(x).append(value)
- remainingArgSignatures = remainingArgSignatures.filter(_.name != key)
- }
- case _ =>
- leftoverArgs.append(rawKey, value)
- }
- }
- }
-
- val missing0 = remainingArgSignatures
- .filter(_.default.isEmpty)
-
- val missing = if(varargs) {
- missing0.filter(_ != argSignatures.last)
- } else {
- missing0.filter(x => incomplete != Some(x))
- }
-
- if (
- incomplete.nonEmpty ||
- missing.nonEmpty ||
- (leftoverArgs.nonEmpty && !varargs)
- ){
- Result.Error.MismatchedArguments(
- missing = missing,
- unknown = leftoverArgs,
- duplicate = Nil,
- incomplete = incomplete
-
- )
- } else {
- val mapping = accumulatedKeywords
- .map{case (k, single) => (k.name, single)}
- .toMap
-
- try invoke0(target, ctx, mapping, leftoverArgs)
- catch{case e: Throwable => Result.Error.Exception(e)}
- }
+ case class EntryPoint[I, T, C](name: String,
+ argSignatures: Seq[ArgSig[I, T, _, C]],
+ doc: Option[String],
+ varargs: Boolean,
+ invoke0: (T, C, Map[String, I]) => Result[Any]){
+ def invoke(target: T, ctx: C, args: Map[String, I]): Result[Any] = {
+ try invoke0(target, ctx, args)
+ catch{case e: Throwable => Result.Error.Exception(e)}
}
}
@@ -141,14 +66,15 @@ object Router{
catch{ case e: Throwable => Left(error(e))}
}
- def read[C]
- (dict: Map[String, Seq[String]],
+ def read[I, C]
+ (dict: Map[String, I],
default: => Option[Any],
- arg: ArgSig[_, _, C],
- thunk: Seq[String] => Any): FailMaybe = {
+ arg: ArgSig[I, _, _, C],
+ thunk: I => Any): FailMaybe = {
arg.reads.arity match{
case 0 =>
- tryEither(thunk(null), Result.ParamError.DefaultFailed(arg, _)).left.map(Seq(_))
+ tryEither(
+ thunk(null.asInstanceOf[I]), Result.ParamError.DefaultFailed(arg, _)).left.map(Seq(_))
case 1 =>
dict.get(arg.name) match{
case None =>
@@ -193,10 +119,10 @@ object Router{
* Invoking the [[EntryPoint]] failed because the arguments provided
* did not line up with the arguments expected
*/
- case class MismatchedArguments(missing: Seq[ArgSig[_, _, _]],
+ case class MismatchedArguments(missing: Seq[ArgSig[_, _, _, _]],
unknown: Seq[String],
- duplicate: Seq[(ArgSig[_, _, _], Seq[String])],
- incomplete: Option[ArgSig[_, _, _]]) extends Error
+ duplicate: Seq[(ArgSig[_, _, _, _], Seq[String])],
+ incomplete: Option[ArgSig[_, _, _, _]]) extends Error
/**
* Invoking the [[EntryPoint]] failed because there were problems
* deserializing/parsing individual arguments
@@ -210,12 +136,12 @@ object Router{
* Something went wrong trying to de-serialize the input parameter;
* the thrown exception is stored in [[ex]]
*/
- case class Invalid(arg: ArgSig[_, _, _], value: Seq[String], ex: Throwable) extends ParamError
+ case class Invalid(arg: ArgSig[_, _, _, _], value: Any, ex: Throwable) extends ParamError
/**
* Something went wrong trying to evaluate the default value
* for this input parameter
*/
- case class DefaultFailed(arg: ArgSig[_, _, _], ex: Throwable) extends ParamError
+ case class DefaultFailed(arg: ArgSig[_, _, _, _], ex: Throwable) extends ParamError
}
}
@@ -233,18 +159,18 @@ object Router{
}
}
- def makeReadCall[C]
- (dict: Map[String, Seq[String]],
+ def makeReadCall[I, C]
+ (dict: Map[String, I],
ctx: C,
default: => Option[Any],
- arg: ArgSig[_, _, C]) = {
- read(dict, default, arg, arg.reads.read(ctx, _))
+ arg: ArgSig[I, _, _, C]) = {
+ read[I, C](dict, default, arg, arg.reads.read(ctx, _))
}
}
-class Router [C <: Context](val c: C) {
+class Router[C <: Context](val c: C) {
import c.universe._
def getValsOrMeths(curCls: Type): Iterable[MethodSymbol] = {
def isAMemberOfAnyRef(member: Symbol) = {
@@ -274,7 +200,8 @@ class Router [C <: Context](val c: C) {
curCls: c.universe.Type,
wrapOutput: c.Tree => c.Tree,
ctx: c.Type,
- argReader: c.Tree): c.universe.Tree = {
+ argReader: c.Tree,
+ annotDeserializeType: c.Tree): c.universe.Tree = {
val baseArgSym = TermName(c.freshName())
val flattenedArgLists = meth.paramss.flatten
def hasDefault(i: Int) = {
@@ -340,7 +267,7 @@ class Router [C <: Context](val c: C) {
}
val argSig = q"""
- cask.Router.ArgSig[$curCls, $docUnwrappedType, $ctx](
+ cask.Router.ArgSig[$annotDeserializeType, $curCls, $docUnwrappedType, $ctx](
${arg.name.toString},
${docUnwrappedType.toString + (if(vararg) "*" else "")},
$docTree,
@@ -378,15 +305,15 @@ class Router [C <: Context](val c: C) {
if (meth.paramLists.isEmpty) q"$baseArgSym.${meth.name.toTermName}"
else q"$baseArgSym.${meth.name.toTermName}(..$argNameCasts)"
val res = q"""
- cask.Router.EntryPoint[$curCls, $ctx](
+ cask.Router.EntryPoint[$annotDeserializeType, $curCls, $ctx](
${meth.name.toString},
scala.Seq(..$argSigs),
${methodDoc match{
- case None => q"scala.None"
- case Some(s) => q"scala.Some($s)"
- }},
+ case None => q"scala.None"
+ case Some(s) => q"scala.Some($s)"
+ }},
${varargs.contains(true)},
- ($baseArgSym: $curCls, ctx: $ctx, $argListSymbol: Map[String, Seq[String]], $extrasSymbol: Seq[String]) =>
+ ($baseArgSym: $curCls, ctx: $ctx, $argListSymbol: Map[String, $annotDeserializeType]) =>
cask.Router.validate(Seq(..$readArgs)) match{
case cask.Router.Result.Success(Seq(..$argNames)) =>
cask.Router.Result.Success(
@@ -394,7 +321,7 @@ class Router [C <: Context](val c: C) {
)
case x: cask.Router.Result.Error => x
}
- )
+ ).asInstanceOf[cask.Router.EntryPoint[Any, $curCls, $ctx]]
"""
c.internal.transform(res){(t, a) =>
diff --git a/cask/src/cask/Routes.scala b/cask/src/cask/Routes.scala
index 4883e34..e539312 100644
--- a/cask/src/cask/Routes.scala
+++ b/cask/src/cask/Routes.scala
@@ -1,17 +1,28 @@
package cask
import language.experimental.macros
-import java.io.OutputStream
+import java.io.{InputStream, OutputStream, OutputStreamWriter, StringWriter}
import cask.Router.EntryPoint
import io.undertow.server.HttpServerExchange
import scala.reflect.macros.blackbox.Context
-import java.io.InputStream
+
+case class Request(cookies: Map[String, Cookie],
+ data: InputStream,
+ queryParams: Map[String, Seq[String]],
+ headers: Map[String, Seq[String]])
case class Response(data: Response.Data,
statusCode: Int = 200,
headers: Seq[(String, String)] = Nil,
- cookies: Seq[Cookie] = Nil)
+ cookies: Seq[Cookie] = Nil) extends BaseResponse
+
+trait BaseResponse{
+ def data: Response.Data
+ def statusCode: Int
+ def headers: Seq[(String, String)]
+ def cookies: Seq[Cookie]
+}
object Response{
implicit def dataResponse[T](t: T)(implicit c: T => Data) = Response(t)
trait Data{
@@ -27,12 +38,18 @@ object Response{
implicit class StreamData(b: InputStream) extends Data{
def write(out: OutputStream) = b.transferTo(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 Routes{
case class EndpointMetadata[T](metadata: Endpoint[_],
- entryPoint: EntryPoint[T, (HttpServerExchange, Seq[String])])
+ entryPoint: EntryPoint[_, T, (HttpServerExchange, Seq[String])])
case class RoutesEndpointsMetadata[T](value: EndpointMetadata[T]*)
object RoutesEndpointsMetadata{
implicit def initialize[T] = macro initializeImpl[T]
@@ -51,13 +68,17 @@ object Routes{
weakTypeOf[T],
(t: router.c.universe.Tree) => q"$annotObjectSym.wrapMethodOutput($t)",
c.weakTypeOf[(io.undertow.server.HttpServerExchange, Seq[String])],
- q"$annotObjectSym.parseMethodInput"
+ q"$annotObjectSym.parseMethodInput",
+ tq"$annotObjectSym.InputType"
)
q"""{
val $annotObjectSym = $annotObject
- cask.Routes.EndpointMetadata($annotObjectSym, $route)
+ cask.Routes.EndpointMetadata(
+ $annotObjectSym,
+ $route
+ )
}"""
}
diff --git a/cask/src/cask/Status.scala b/cask/src/cask/Status.scala
index a1915b1..f3f5434 100644
--- a/cask/src/cask/Status.scala
+++ b/cask/src/cask/Status.scala
@@ -46,6 +46,7 @@ object Status {
420 -> EnhanceYourCalm,
429 -> TooManyRequests,
451 -> UnavailableForLegalReasons,
+ 500 -> InternalServerError,
501 -> NotImplemented,
502 -> BadGateway,
503 -> ServiceUnavailable,
diff --git a/cask/test/src/test/cask/FormJsonPost.scala b/cask/test/src/test/cask/FormJsonPost.scala
new file mode 100644
index 0000000..2874a52
--- /dev/null
+++ b/cask/test/src/test/cask/FormJsonPost.scala
@@ -0,0 +1,19 @@
+package test.cask
+
+import cask.FormValue
+import io.undertow.server.HttpServerExchange
+
+object FormJsonPost extends cask.MainRoutes{
+ @cask.postJson("/json")
+ def jsonEndpoint(x: HttpServerExchange, value1: ujson.Js.Value, value2: Seq[Int]) = {
+ "OK " + value1 + " " + value2
+ }
+
+ @cask.postForm("/form")
+ def formEndpoint(x: HttpServerExchange, value1: FormValue, value2: Seq[Int]) = {
+ "OK " + value1 + " " + value2
+ }
+
+ initialize()
+}
+