Skip to content

Commit

Permalink
Strip the type params from the AST and differ them to the builder types
Browse files Browse the repository at this point in the history
It turns out the types are not that generally useful in the ASTs themselves and actually only used in the builders. This can be seen in the use of existentials, many of which are now removed.
  • Loading branch information
bryce-anderson committed Jul 6, 2014
1 parent 011867f commit 175f877
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 151 deletions.
35 changes: 19 additions & 16 deletions core/src/main/scala/org/http4s/rho/PathBuilder.scala
Original file line number Diff line number Diff line change
@@ -1,56 +1,59 @@
package org.http4s
package rho

import scala.language.existentials

import bits.QueryAST.{QueryRule, EmptyQuery}
import org.http4s.rho.bits.QueryAST.{TypedQuery, QueryRule, EmptyQuery}
import bits.HeaderAST._
import bits.PathAST._
import bits._
import PathAST.MetaCons

import shapeless.{::, HList}
import shapeless.{HNil, ::, HList}
import shapeless.ops.hlist.Prepend

/** The goal of a PathBuilder is to allow the composition of what is typically on the status line
* of a HTTP request. That includes the request method, path, and query params.
*/

/** Fully functional path building */
final class PathBuilder[T <: HList](val method: Method, val path: PathRule[T])
final class PathBuilder[T <: HList](val method: Method, val path: PathRule)
extends HeaderAppendable[T]
with RouteExecutable[T]
with MetaDataSyntax
{
type Self = PathBuilder[T]

override def validators: HeaderRule[_ <: HList] = EmptyHeaderRule
override def validators: HeaderRule = EmptyHeaderRule

override def query: QueryRule = EmptyQuery

def +?[T1 <: HList](q: QueryRule[T1])(implicit prep: Prepend[T1,T]): QueryBuilder[prep.Out] =
QueryBuilder(method, path, q)
def +?[T1 <: HList](q: TypedQuery[T1])(implicit prep: Prepend[T1,T]): QueryBuilder[prep.Out] =
QueryBuilder(method, path, q.rule)

def /(t: CaptureTail) : Router[List[String]::T] = new Router(method, PathAnd(path,t), EmptyQuery, EmptyHeaderRule)

def /(s: String): PathBuilder[T] = new PathBuilder(method, PathAnd(path, PathMatch(s)))

def /(s: Symbol): PathBuilder[String::T] =
new PathBuilder(method, PathAnd(path, MetaCons(PathCapture(StringParser.strParser), TextMeta(s.name))))
def /(s: Symbol): PathBuilder[String::T] = {
val capture = PathCapture(StringParser.strParser, implicitly[Manifest[String]])
new PathBuilder(method, PathAnd(path, MetaCons(capture, TextMeta(s.name))))
}

def /[T2 <: HList](t: PathRule[T2])(implicit prep: Prepend[T2, T]) : PathBuilder[prep.Out] =
new PathBuilder(method, PathAnd(path,t))
def /[T2 <: HList](t: TypedPath[T2])(implicit prep: Prepend[T2, T]) : PathBuilder[prep.Out] =
new PathBuilder(method, PathAnd(path, t.rule))

def /[T2 <: HList](t: PathBuilder[T2])(implicit prep: Prepend[T2, T]) : PathBuilder[prep.Out] =
new PathBuilder(method, PathAnd(path, t.path))

override def addMetaData(data: Metadata): PathBuilder[T] =
new PathBuilder[T](method, MetaCons(path, data))

override def >>>[T1 <: HList](h2: HeaderRule[T1])(implicit prep: Prepend[T1,T]): Router[prep.Out] = validate(h2)
override def >>>[T1 <: HList](h2: TypedHeader[T1])(implicit prep: Prepend[T1,T]): Router[prep.Out] =
validate(h2)

def toAction: Router[T] = validate(EmptyHeaderRule)
def toAction: Router[T] = validate(TypedHeader[HNil](EmptyHeaderRule))

def validate[T1 <: HList](h2: HeaderRule[T1])(implicit prep: Prepend[T1,T]): Router[prep.Out] =
Router(method, path, EmptyQuery, h2)
def validate[T1 <: HList](h2: TypedHeader[T1])(implicit prep: Prepend[T1,T]): Router[prep.Out] =
Router(method, path, EmptyQuery, h2.rule)

def decoding[R](dec: Decoder[R]): CodecRouter[T, R] = CodecRouter(toAction, dec)

Expand Down
15 changes: 7 additions & 8 deletions core/src/main/scala/org/http4s/rho/QueryBuilder.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.http4s
package rho

import scala.language.existentials

import org.http4s.rho.bits.{MetaDataSyntax, Metadata, HListToFunc, HeaderAppendable}
import bits.PathAST._
Expand All @@ -15,8 +14,8 @@ import shapeless.{::, HList}


case class QueryBuilder[T <: HList](method: Method,
path: PathRule[_ <: HList],
query: QueryRule[_ <: HList])
path: PathRule,
query: QueryRule)
extends RouteExecutable[T]
with HeaderAppendable[T]
with MetaDataSyntax
Expand All @@ -26,13 +25,13 @@ case class QueryBuilder[T <: HList](method: Method,
override def makeAction[F](f: F, hf: HListToFunc[T, F]): RhoAction[T, F] =
RhoAction(Router(method, path, query, validators), f, hf)

override def >>>[T1 <: HList](v: HeaderRule[T1])(implicit prep1: Prepend[T1, T]): Router[prep1.Out] =
Router(method, path, query, v)
override def >>>[T1 <: HList](v: TypedHeader[T1])(implicit prep1: Prepend[T1, T]): Router[prep1.Out] =
Router(method, path, query, v.rule)

override def addMetaData(data: Metadata): Self = QueryBuilder(method, path, MetaCons(query, data))

def &[T1 <: HList](rule: QueryRule[T1])(implicit prep: Prepend[T1, T]): QueryBuilder[prep.Out] =
QueryBuilder(method, path, QueryAnd(query, rule))
def &[T1 <: HList](q: TypedQuery[T1])(implicit prep: Prepend[T1, T]): QueryBuilder[prep.Out] =
QueryBuilder(method, path, QueryAnd(query, q.rule))

override def validators: HeaderRule[_ <: HList] = EmptyHeaderRule
override def validators: HeaderRule = EmptyHeaderRule
}
4 changes: 2 additions & 2 deletions core/src/main/scala/org/http4s/rho/RhoAction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import shapeless.HList

case class RhoAction[T <: HList, F](router: RouteExecutable[T], f: F, hf: HListToFunc[T, F]) {
final def method: Method = router.method
final def path: PathRule[_ <: HList] = router.path
final def validators: HeaderRule[_ <: HList] = router.validators
final def path: PathRule = router.path
final def validators: HeaderRule = router.validators
final def responseEncodings: Seq[MediaType] = hf.encodings
final def responseType: Option[Manifest[_]] = hf.manifest
final def decoders: Seq[MediaType] = router match {
Expand Down
43 changes: 22 additions & 21 deletions core/src/main/scala/org/http4s/rho/RouteExecutor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ trait ExecutableCompiler {
//////////////////////// Stuff for executing the route //////////////////////////////////////

/** The untyped guts of ensureValidHeaders and friends */
protected def runValidation(req: Request, v: HeaderRule[_ <: HList], stack: HList): \/[String,HList] = {
protected def runValidation(req: Request, v: HeaderRule, stack: HList): \/[String,HList] = {
import bits.HeaderAST.MetaCons
v match {
case HeaderAnd(a, b) => runValidation(req, a, stack).flatMap(runValidation(req, b, _))
Expand Down Expand Up @@ -59,14 +59,14 @@ trait ExecutableCompiler {
}
}

protected def runQuery(req: Request, v: QueryRule[_ <: HList], stack: HList): String\/HList = {
protected def runQuery(req: Request, v: QueryRule, stack: HList): String\/HList = {
import QueryAST.MetaCons
v match {
case QueryAnd(a, b) => runQuery(req, a, stack).flatMap(runQuery(req, b, _))

case QueryOr(a, b) => runQuery(req, a, stack).orElse(runQuery(req, b, stack))

case QueryCapture(name, parser, default) => parser.collect(name, req.multiParams, default).map(_ :: stack)
case QueryCapture(name, parser, default, _) => parser.collect(name, req.multiParams, default).map(_ :: stack)

case MetaCons(r, _) => runQuery(req, r, stack)

Expand All @@ -75,7 +75,7 @@ trait ExecutableCompiler {
}

/** Runs the URL and pushes values to the HList stack */
protected def runPath[T1<: HList](req: Request, v: PathRule[T1], path: List[String]): Option[\/[String,T1]] = {
protected def runPath(req: Request, v: PathRule, path: List[String]): Option[\/[String, HList]] = {

// setup a stack for the path
var currentPath = path
Expand All @@ -86,14 +86,14 @@ trait ExecutableCompiler {
}

// WARNING: returns null if not matched but no nulls should escape the runPath method
def go(v: PathRule[_ <: HList], stack: HList): \/[String,HList] = {
def go(v: PathRule, stack: HList): \/[String,HList] = {
import PathAST.MetaCons
v match {
case PathAnd(a, b) =>
val v = go(a, stack)
if (v == null) null
else if (!currentPath.isEmpty ||
b.isInstanceOf[PathAnd[_]] ||
b.isInstanceOf[PathAnd] ||
b.isInstanceOf[CaptureTail]) v.flatMap(go(b, _))
else null

Expand All @@ -106,7 +106,7 @@ trait ExecutableCompiler {
go(b, stack)
}

case PathCapture(f) => f.parse(pop).map{ i => i::stack}
case PathCapture(f, _) => f.parse(pop).map{ i => i::stack}

case PathMatch("") => \/-(stack) // "" is consider a NOOP

Expand Down Expand Up @@ -134,7 +134,7 @@ trait ExecutableCompiler {
val r = go(v, HNil)
if (currentPath.isEmpty) r match {
case null => None
case r@ \/-(_) => Some(r.asInstanceOf[\/[String,T1]])
case r@ \/-(_) => Some(r.asInstanceOf[\/[String,HList]])
case r@ -\/(_) => Some(r)
} else None
}
Expand All @@ -158,8 +158,8 @@ private[rho] class RouteExecutor[F] extends ExecutableCompiler
protected def compileRouter[T <: HList, F](r: Router[T], f: F, hf: HListToFunc[T, F]): Result = {
val readyf = hf.conv(f)
val ff: Result = { req =>
pathAndValidate[T](req, r.path, r.query, r.validators).map(_ match {
case \/-(stack) => readyf(req,stack)
pathAndValidate(req, r.path, r.query, r.validators).map(_ match {
case \/-(stack) => readyf(req, stack.asInstanceOf[T])
case -\/(s) => onBadRequest(s)
})
}
Expand All @@ -171,17 +171,18 @@ private[rho] class RouteExecutor[F] extends ExecutableCompiler
val actionf = hf.conv(f)
val allvals = {
if (!r.decoder.force) {
val mediaReq = r.decoder.consumes.map { mediaType =>
val mediaReq: Seq[HeaderRule] = r.decoder.consumes.map { mediaType =>
HeaderRequire(Header.`Content-Type`, { h: Header.`Content-Type`.HeaderT => h.mediaType == mediaType })
}
HeaderAnd(r.router.validators, mediaReq.tail.foldLeft(mediaReq.head:HeaderRule[HNil])(HeaderOr(_, _)))
if (mediaReq.isEmpty) r.router.validators
else HeaderAnd(r.router.validators, mediaReq.tail.foldLeft(mediaReq.head)(HeaderOr(_, _)))
}
else r.router.validators
}
val ff: Result = { req =>
pathAndValidate[T](req, r.router.path, r.router.query, allvals).map(_ match {
pathAndValidate(req, r.router.path, r.router.query, allvals).map(_ match {
case \/-(stack) => r.decoder.decode(req).flatMap(_ match {
case \/-(r) => actionf(req,r::stack)
case \/-(r) => actionf(req,r::stack.asInstanceOf[T])
case -\/(e) => onBadRequest(s"Error decoding body: $e")
})
case -\/(s) => onBadRequest(s)
Expand All @@ -191,10 +192,10 @@ private[rho] class RouteExecutor[F] extends ExecutableCompiler
ff
}

private def pathAndValidate[T <: HList](req: Request,
path: PathRule[_ <: HList],
query: QueryRule[_ <: HList],
v: HeaderRule[_ <: HList]): Option[\/[String, T]] =
private def pathAndValidate(req: Request,
path: PathRule,
query: QueryRule,
v: HeaderRule): Option[\/[String, HList]] =
{
val p = parsePath(req.requestUri.path)

Expand All @@ -204,10 +205,10 @@ private[rho] class RouteExecutor[F] extends ExecutableCompiler
j <- runQuery(req, query, i)
k <- runValidation(req, v, j)
} yield k
}.asInstanceOf[Option[\/[String, T]]]
}.asInstanceOf[Option[\/[String, HList]]]
}

/** Walks the validation tree */
def ensureValidHeaders[T1 <: HList](v: HeaderRule[T1], req: Request): \/[String,T1] =
runValidation(req, v, HNil).asInstanceOf[\/[String,T1]]
def ensureValidHeaders(v: HeaderRule, req: Request): \/[String, HList] =
runValidation(req, v, HNil).asInstanceOf[\/[String,HList]]
}
28 changes: 15 additions & 13 deletions core/src/main/scala/org/http4s/rho/Router.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.http4s
package rho

import scala.language.existentials

import bits.PathAST._
import bits.HeaderAST._
import bits.QueryAST.QueryRule
Expand All @@ -21,18 +19,18 @@ import shapeless.ops.hlist.Prepend
* @tparam T cumulative type of the required method for executing the router
*/
case class Router[T <: HList](method: Method,
val path: PathRule[_ <: HList],
val query: QueryRule[_ <: HList],
validators: HeaderRule[_ <: HList])
val path: PathRule,
val query: QueryRule,
validators: HeaderRule)
extends RouteExecutable[T]
with HeaderAppendable[T]
with MetaDataSyntax
{

type Self = Router[T]

override def >>>[T2 <: HList](v: HeaderRule[T2])(implicit prep1: Prepend[T2, T]): Router[prep1.Out] =
Router(method, path, query, HeaderAnd(validators,v))
override def >>>[T2 <: HList](v: TypedHeader[T2])(implicit prep1: Prepend[T2, T]): Router[prep1.Out] =
Router(method, path, query, HeaderAnd(validators, v.rule))

override def makeAction[F](f: F, hf: HListToFunc[T, F]): RhoAction[T, F] =
new RhoAction(this, f, hf)
Expand All @@ -53,34 +51,38 @@ case class CodecRouter[T <: HList, R](router: Router[T], decoder: Decoder[R])
override def addMetaData(data: Metadata): CodecRouter[T, R] =
CodecRouter(router.addMetaData(data), decoder)

override def >>>[T2 <: HList](v: HeaderRule[T2])(implicit prep1: Prepend[T2, T]): CodecRouter[prep1.Out,R] =
override def >>>[T2 <: HList](v: TypedHeader[T2])(implicit prep1: Prepend[T2, T]): CodecRouter[prep1.Out,R] =
CodecRouter(router >>> v, decoder)

override def makeAction[F](f: F, hf: HListToFunc[R::T, F]): RhoAction[R::T, F] =
new RhoAction(this, f, hf)

override def path: PathRule[_ <: HList] = router.path
override def path: PathRule = router.path

override def method: Method = router.method

override def query: QueryRule = router.query

def decoding(decoder2: Decoder[R]): CodecRouter[T, R] = CodecRouter(router, decoder.or(decoder2))

override val validators: HeaderRule[_ <: HList] = {
override val validators: HeaderRule = {
if (!decoder.consumes.isEmpty) {
val mt = requireThat(Header.`Content-Type`){ h: Header.`Content-Type`.HeaderT =>
decoder.consumes.find(_ == h.mediaType).isDefined
}
HeaderAnd(router.validators, mt)
HeaderAnd(router.validators, mt.rule)
} else router.validators
}
}

private[rho] trait RouteExecutable[T <: HList] {
def method: Method

def path: PathRule[_ <: HList]
def path: PathRule

def query: QueryRule

def validators: HeaderRule[_ <: HList]
def validators: HeaderRule

def makeAction[F](f: F, hf: HListToFunc[T, F]): RhoAction[T, F]

Expand Down
Loading

2 comments on commit 175f877

@arouel
Copy link
Contributor

@arouel arouel commented on 175f877 Jul 6, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. It seems that it makes things easier.

@bryce-anderson
Copy link
Member Author

@bryce-anderson bryce-anderson commented on 175f877 Jul 6, 2014 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.