Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.19 #253

Merged
merged 24 commits into from
Nov 4, 2018
Merged

0.19 #253

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
zarthross marked this conversation as resolved.
Show resolved Hide resolved

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] =
chuwy marked this conversation as resolved.
Show resolved Hide resolved
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