Skip to content

Commit

Permalink
Merge pull request #253 from chuwy/0.19
Browse files Browse the repository at this point in the history
0.19
  • Loading branch information
zarthross authored Nov 4, 2018
2 parents 0c9ae59 + 00b17d2 commit a12cbe1
Show file tree
Hide file tree
Showing 40 changed files with 522 additions and 514 deletions.
53 changes: 23 additions & 30 deletions core/src/main/scala/org/http4s/rho/AuthedContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,31 @@ package rho

import cats.Monad
import cats.data.{Kleisli, OptionT}
import org.http4s.rho.bits.{FailureResponseOps, SuccessResponse, TypedHeader}
import shapeless.{::, HNil}

import org.http4s.rho.bits.{FailureResponseOps, SuccessResponse, TypedHeader}


/** The [[AuthedContext]] provides a convenient way to define a RhoService
/** The [[AuthedContext]] provides a convenient way to define a RhoRoutes
* which works with http4s authentication middleware.
* Please note that `AuthMiddleware`-wrapping is mandatory, otherwise context
* doesn't take effect.
* {{{
* case class User(name: String, id: UUID)
*
* object Auth {
* val authUser: Service[Request, User] = Kleisli({ _ =>
* Task.now(User("Test User", UUID.randomUUID()))
* })
*
* val authenticated = AuthMiddleware(authUser)
* val middleware = AuthMiddleware { req =>
* OptionT(IO(User("Bob", UUID.randomUUID())))
* }
*
* object MyAuth extends AuthedContext[User]
* object Auth extends AuthedContext[IO, User]
*
* object MyService extends RhoService {
* import MyAuth._
* GET +? param("foo", "bar") |>> { (req: Request, foo: String) =>
* val user = getAuth(req)
* if (user.name == "Test User") {
* Ok(s"just root with parameter 'foo=\$foo'")
* } else {
* BadRequest("This should not have happened.")
* }
* object BobRoutes extends RhoRoutes[IO] {
* GET +? param("foo", "bar") >>> Auth.auth |>> { (foo: String, user: User) =>
* Ok(s"Bob with id ${user.id}, foo $foo")
* }
* }
*
* val service = Auth.authenticated(MyAuth.toService(MyService.toService(SwaggerSupport())))
* val service = middleware.apply(Auth.toService(BobRoutes.toRoutes()))
* }}}
*
* @tparam U authInfo type for this service.
Expand All @@ -44,26 +37,26 @@ class AuthedContext[F[_]: Monad, U] extends FailureResponseOps[F] {
/* Attribute key to lookup authInfo in request attributeMap . */
final private val authKey = AttributeKey[U]

/** Turn the [[HttpService]] into an `AuthedService`
/** Turn the [[HttpRoutes]] into an `AuthedService`
*
* @param service [[HttpService]] to convert
* @param routes [[HttpRoutes]] to convert
* @return An `AuthedService` which can be mounted by http4s servers.
*/
def toService(service: HttpService[F]): AuthedService[U, F] = {
def toService(routes: HttpRoutes[F]): AuthedService[U, F] = {
type O[A] = OptionT[F, A]

Kleisli[O, AuthedRequest[F, U], Response[F]] { (a: AuthedRequest[F, U]) =>
service(a.req.withAttribute[U](authKey, a.authInfo))
Kleisli[O, AuthedRequest[F, U], Response[F]] { a: AuthedRequest[F, U] =>
routes(a.req.withAttribute[U](authKey, a.authInfo))
}
}

/* Get the authInfo object from request. */
def getAuth(req: Request[F]): U = {
req.attributes.get[U](authKey).get
}
/** Get the authInfo object from request if `AuthMiddleware` provided one */
def getAuth(req: Request[F]): Option[U] =
req.attributes.get(authKey)

def auth(): TypedHeader[F, U :: HNil] = RhoDsl[F].genericRequestHeaderCapture[U] { req =>
req.attributes.get(authKey) match {
/** Request matcher to capture authentication information */
def auth: TypedHeader[F, U :: HNil] = RhoDsl[F].genericRequestHeaderCapture[U] { req =>
getAuth(req) match {
case Some(authInfo) => SuccessResponse(authInfo)
case None => error("Invalid auth configuration")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ package rho

import cats.Monad
import cats.data.Kleisli
import shapeless.HList

import org.http4s.rho.RhoRoute.Tpe
import org.http4s.rho.bits.PathTree
import shapeless.HList

/** Transforms a [[RhoRoute]] into an `RouteType`.
*
* This can be a stateful operation, storing the action for later execution
* or any other type of compilation phase.
*/
trait CompileService[F[_], RouteType] {
trait CompileRoutes[F[_], RouteType] {

/** Transform the [[RhoRoute]] into a `RouteType` possibly mutating this compilers state.
*
Expand All @@ -23,27 +24,25 @@ trait CompileService[F[_], RouteType] {
def compile[T <: HList](route: RhoRoute[F, T]): RouteType
}

object CompileService {
object CompileRoutes {

/** [[CompileService]] that simply returns its argument */
def identityCompiler[F[_]]: CompileService[F, Tpe[F]] = new CompileService[F, RhoRoute.Tpe[F]] {
/** [[CompileRoutes]] that simply returns its argument */
def identityCompiler[F[_]]: CompileRoutes[F, Tpe[F]] = new CompileRoutes[F, RhoRoute.Tpe[F]] {
def compile[T <: HList](route: RhoRoute[F, T]): RhoRoute[F, T] = route
}

/** Importable implicit identity compiler */
object Implicit {
implicit def compiler[F[_]]: CompileService[F, RhoRoute.Tpe[F]] = identityCompiler[F]
implicit def compiler[F[_]]: CompileRoutes[F, RhoRoute.Tpe[F]] = identityCompiler[F]
}


/** Convert the `Seq` of [[RhoRoute]]'s into a `HttpService`
/** Convert the `Seq` of [[RhoRoute]]'s into a `HttpRoutes`
*
* @param routes `Seq` of routes to bundle into a service.
* @param filter [[RhoMiddleware]] to apply to the routes.
* @return An `HttpService`
* @return An `HttpRoutes`
*/
def foldServices[F[_]: Monad](routes: Seq[RhoRoute.Tpe[F]], filter: RhoMiddleware[F]): HttpService[F] = {
val tree = filter(routes).foldLeft(PathTree[F]()){ (t, r) => t.appendRoute(r) }
def foldRoutes[F[_]: Monad](routes: Seq[RhoRoute.Tpe[F]]): HttpRoutes[F] = {
val tree = routes.foldLeft(PathTree[F]()){ (t, r) => t.appendRoute(r) }
Kleisli((req: Request[F]) => tree.getResult(req).toResponse)
}
}
6 changes: 5 additions & 1 deletion core/src/main/scala/org/http4s/rho/Result.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ trait ResultSyntaxInstances[F[_]] {
override def withAttribute[A](key: AttributeKey[A], value: A)(implicit F: Functor[F]): Self =
Result(r.resp.withAttribute(key, value))

def withEntity[E](b: E)(implicit w: EntityEncoder[F, E]): Self =
Result(r.resp.withEntity(b))

@deprecated("Use withEntity", "0.19")
def withBody[U](b: U)(implicit F: Monad[F], w: EntityEncoder[F, U]): F[Self] =
F.map(r.resp.withBody(b))(Result(_))
F.pure(Result(r.resp.withEntity(b)))
}
}
58 changes: 58 additions & 0 deletions core/src/main/scala/org/http4s/rho/RhoRoutes.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.http4s
package rho

import cats.Monad
import org.http4s.rho.bits.PathAST.TypedPath
import org.log4s.getLogger
import shapeless.{HList, HNil}

/** Constructor class for defining routes
*
* The [[RhoRoutes]] provides a convenient way to define routes in a style
* similar to scalatra etc by providing implicit conversions and an implicit
* [[CompileRoutes]] inside the constructor.
*
* {{{
* new RhoRoutes[IO] {
* POST / "foo" / pathVar[Int] +? param[String]("param") |>> { (p1: Int, param: String) =>
* Ok("success")
* }
* }
* }}}
*
* @param routes Routes to prepend before elements in the constructor.
*/
class RhoRoutes[F[_]: Monad](routes: Seq[RhoRoute[F, _ <: HList]] = Vector.empty)
extends bits.MethodAliases
with bits.ResponseGeneratorInstances[F]
with RoutePrependable[F, RhoRoutes[F]]
with EntityEncoderInstances
with RhoDsl[F]
{
final private val routesBuilder = RoutesBuilder[F](routes)

final protected val logger = getLogger

final implicit protected def compileRoutes: CompileRoutes[F, RhoRoute.Tpe[F]] = routesBuilder

/** Create a new [[RhoRoutes]] by appending the routes of the passed [[RhoRoutes]]
*
* @param other [[RhoRoutes]] whos routes are to be appended.
* @return A new [[RhoRoutes]] that contains the routes of the other service appended
* the the routes contained in this service.
*/
final def and(other: RhoRoutes[F]): RhoRoutes[F] = new RhoRoutes(this.getRoutes ++ other.getRoutes)

/** Get a snapshot of the collection of [[RhoRoute]]'s accumulated so far */
final def getRoutes: Seq[RhoRoute[F, _ <: HList]] = routesBuilder.routes()

/** Convert the [[RhoRoute]]'s accumulated into a `HttpRoutes` */
final def toRoutes(middleware: RhoMiddleware[F] = identity): HttpRoutes[F] =
routesBuilder.toRoutes(middleware)

final override def toString: String = s"RhoRoutes(${routesBuilder.routes().toString()})"

final override def /:(prefix: TypedPath[F, HNil]): RhoRoutes[F] = {
new RhoRoutes(routesBuilder.routes().map { prefix /: _ })
}
}
58 changes: 0 additions & 58 deletions core/src/main/scala/org/http4s/rho/RhoService.scala

This file was deleted.

2 changes: 1 addition & 1 deletion core/src/main/scala/org/http4s/rho/RouteExecutable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ trait RouteExecutable[F[_], T <: HList] extends TypedBuilder[F, T] { exec =>
def makeRoute(action: Action[F, T]): RhoRoute[F, T]

/** Compiles a HTTP request definition into an action */
final def |>>[U, R](f: U)(implicit hltf: HListToFunc[F, T, U], srvc: CompileService[F, R]): R =
final def |>>[U, R](f: U)(implicit hltf: HListToFunc[F, T, U], srvc: CompileRoutes[F, R]): R =
srvc.compile(makeRoute(hltf.toAction(f)))
}
57 changes: 57 additions & 0 deletions core/src/main/scala/org/http4s/rho/RoutesBuilder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.http4s.rho

import scala.collection.immutable.VectorBuilder
import cats.Monad
import shapeless.HList
import org.http4s._

/** CompileRoutes which accumulates routes and can build a `HttpRoutes` */
final class RoutesBuilder[F[_]: Monad] private(internalRoutes: VectorBuilder[RhoRoute.Tpe[F]]) extends CompileRoutes[F, RhoRoute.Tpe[F]] {

/** Turn the accumulated routes into an `HttpRoutes`
*
* @param middleware [[RhoMiddleware]] to apply to the collection of routes.
* @return An `HttpRoutes` which can be mounted by http4s servers.
*/
def toRoutes(middleware: RhoMiddleware[F] = identity): HttpRoutes[F] =
CompileRoutes.foldRoutes(middleware.apply(internalRoutes.result()))

/** Get a snapshot of the currently acquired routes */
def routes(): Seq[RhoRoute.Tpe[F]] = internalRoutes.result()

/** Append the routes into this [[RoutesBuilder]]
*
* @param routes Routes to accumulate.
* @return `this` instance with its internal state mutated.
*/
def append(routes: TraversableOnce[RhoRoute.Tpe[F]]): this.type = {
internalRoutes ++= routes
this
}

/** Accumulate the [[RhoRoute]] into this [[RoutesBuilder]]
*
* This is the same as appending a the single route and returning the same route.
*
* @param route [[RhoRoute]] to compile.
* @tparam T `HList` representation of the result of the route
* @return The [[RhoRoute]] passed to the method.
*/
override def compile[T <: HList](route: RhoRoute[F, T]): RhoRoute[F, T] = {
internalRoutes += route
route
}
}

object RoutesBuilder {
/** Constructor method for new `RoutesBuilder` instances */
def apply[F[_]: Monad](): RoutesBuilder[F] = apply(Seq.empty)

/** Constructor method for new `RoutesBuilder` instances with existing routes */
def apply[F[_]: Monad](routes: Seq[RhoRoute.Tpe[F]]): RoutesBuilder[F] = {
val builder = new VectorBuilder[RhoRoute.Tpe[F]]
builder ++= routes

new RoutesBuilder(builder)
}
}
Loading

0 comments on commit a12cbe1

Please sign in to comment.