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

Generalize AuthedRequest to ContextRequest #2612

Merged
merged 4 commits into from Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 5 additions & 8 deletions core/src/main/scala/org/http4s/AuthedRequest.scala
@@ -1,16 +1,13 @@
package org.http4s

import cats.{Functor, ~>}
import cats.Functor
import cats.data.Kleisli
import cats.implicits._

final case class AuthedRequest[F[_], A](authInfo: A, req: Request[F]) {
def mapK[G[_]](fk: F ~> G): AuthedRequest[G, A] =
AuthedRequest(authInfo, req.mapK(fk))
}

object AuthedRequest {
def apply[F[_]: Functor, T](
getUser: Request[F] => F[T]): Kleisli[F, Request[F], AuthedRequest[F, T]] =
Kleisli(request => getUser(request).map(user => AuthedRequest(user, request)))
ContextRequest[F, T](getUser)

def apply[F[_], T](context: T, req: Request[F]): AuthedRequest[F, T] =
ContextRequest[F, T](context, req)
}
11 changes: 5 additions & 6 deletions core/src/main/scala/org/http4s/AuthedRoutes.scala
@@ -1,9 +1,8 @@
package org.http4s

import cats.Applicative
import cats.{Applicative, Defer}
import cats.data.{Kleisli, OptionT}
import cats.implicits._
import cats.effect.Sync

object AuthedRoutes {

Expand All @@ -17,8 +16,8 @@ object AuthedRoutes {
* @return an [[AuthedRoutes]] that wraps `run`
*/
def apply[T, F[_]](run: AuthedRequest[F, T] => OptionT[F, Response[F]])(
implicit F: Sync[F]): AuthedRoutes[T, F] =
Kleisli(req => OptionT(F.suspend(run(req).value)))
implicit F: Defer[F]): AuthedRoutes[T, F] =
Kleisli(req => OptionT(F.defer(run(req).value)))

/** Lifts a partial function into an [[AuthedRoutes]]. The application of the
* partial function is suspended in `F` to permit more efficient combination
Expand All @@ -30,8 +29,8 @@ object AuthedRoutes {
* wherever `pf` is defined, an `OptionT.none` wherever it is not
*/
def of[T, F[_]](pf: PartialFunction[AuthedRequest[F, T], F[Response[F]]])(
implicit F: Sync[F]): AuthedRoutes[T, F] =
Kleisli(req => OptionT(F.suspend(pf.lift(req).sequence)))
implicit F: Defer[F], FA: Applicative[F]): AuthedRoutes[T, F] =
Kleisli(req => OptionT(F.defer(pf.lift(req).sequence)))

/**
* The empty service (all requests fallthrough).
Expand Down
20 changes: 20 additions & 0 deletions core/src/main/scala/org/http4s/ContextRequest.scala
@@ -0,0 +1,20 @@
package org.http4s

import cats.syntax.functor._
import cats.{Functor, ~>}
import cats.data.Kleisli

final case class ContextRequest[F[_], A](context: A, req: Request[F]) {
def mapK[G[_]](fk: F ~> G): ContextRequest[G, A] =
ContextRequest(context, req.mapK(fk))

@deprecated("Use context instead", "0.21.0")
def authInfo: A = context
Copy link
Member

Choose a reason for hiding this comment

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

Seeing how many places this changed just in this repo, it would be a great candidate for a scalafix, but that's not essential to this PR.


}

object ContextRequest {
def apply[F[_]: Functor, T](
getContext: Request[F] => F[T]): Kleisli[F, Request[F], ContextRequest[F, T]] =
Kleisli(request => getContext(request).map(ctx => ContextRequest(ctx, request)))
}
44 changes: 44 additions & 0 deletions core/src/main/scala/org/http4s/ContextRoutes.scala
@@ -0,0 +1,44 @@
package org.http4s

import cats.data.{Kleisli, OptionT}
import cats.{Applicative, Defer}
import cats.implicits._

object ContextRoutes {

/** Lifts a function into an [[ContextRoutes]]. The application of `run`
* is suspended in `F` to permit more efficient combination of
* routes via `SemigroupK`.
*
* @tparam F the effect of the [[ContextRoutes]]
* @tparam T the type of the auth info in the [[ContextRequest]] accepted by the [[ContextRoutes]]
* @param run the function to lift
* @return an [[ContextRoutes]] that wraps `run`
*/
def apply[T, F[_]](run: ContextRequest[F, T] => OptionT[F, Response[F]])(
implicit F: Defer[F]): ContextRoutes[T, F] =
Kleisli(req => OptionT(F.defer(run(req).value)))

/** Lifts a partial function into an [[ContextRoutes]]. The application of the
* partial function is suspended in `F` to permit more efficient combination
* of authed services via `SemigroupK`.
*
* @tparam F the base effect of the [[ContextRoutes]]
* @param pf the partial function to lift
* @return An [[ContextRoutes]] that returns some [[Response]] in an `OptionT[F, ?]`
* wherever `pf` is defined, an `OptionT.none` wherever it is not
*/
def of[T, F[_]](pf: PartialFunction[ContextRequest[F, T], F[Response[F]]])(
implicit F: Defer[F], FA: Applicative[F]): ContextRoutes[T, F] =
Kleisli(req => OptionT(F.defer(pf.lift(req).sequence)))

/**
* The empty service (all requests fallthrough).
*
* @tparam T - ignored.
* @return
*/
def empty[T, F[_]: Applicative]: ContextRoutes[T, F] =
Kleisli.liftF(OptionT.none)

}
4 changes: 4 additions & 0 deletions core/src/main/scala/org/http4s/package.scala
Expand Up @@ -55,6 +55,8 @@ package object http4s { // scalastyle:ignore
@deprecated("Deprecated in favor of HttpRoutes", "0.19")
type HttpService[F[_]] = HttpRoutes[F]

type AuthedRequest[F[_], T] = ContextRequest[F, T]

/**
* The type parameters need to be in this order to make partial unification
* trigger. See https://github.com/http4s/http4s/issues/1506
Expand All @@ -64,6 +66,8 @@ package object http4s { // scalastyle:ignore
@deprecated("Deprecated in favor of AuthedRoutes", "0.20.1")
type AuthedService[T, F[_]] = AuthedRoutes[T, F]

type ContextRoutes[T, F[_]] = Kleisli[OptionT[F, ?], ContextRequest[F, T], Response[F]]

type Callback[A] = Either[Throwable, A] => Unit

/** A stream of server-sent events */
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/auth.md
Expand Up @@ -131,7 +131,7 @@ error handling, we recommend an error [ADT] instead of a `String`.
```tut:silent
val authUser: Kleisli[IO, Request[IO], Either[String,User]] = Kleisli(_ => IO(???))

val onFailure: AuthedRoutes[String, IO] = Kleisli(req => OptionT.liftF(Forbidden(req.authInfo)))
val onFailure: AuthedRoutes[String, IO] = Kleisli(req => OptionT.liftF(Forbidden(req.context)))
val middleware = AuthMiddleware(authUser, onFailure)

val service: HttpRoutes[IO] = middleware(authedRoutes)
Expand Down
2 changes: 1 addition & 1 deletion dsl/src/main/scala/org/http4s/dsl/impl/Auth.scala
Expand Up @@ -5,6 +5,6 @@ import org.http4s.{AuthedRequest, Request}
trait Auth {
object as {
def unapply[F[_], A](ar: AuthedRequest[F, A]): Option[(Request[F], A)] =
Some(ar.req -> ar.authInfo)
Some(ar.req -> ar.context)
}
}
11 changes: 11 additions & 0 deletions server/src/main/scala/org/http4s/server/ContextMiddleware.scala
@@ -0,0 +1,11 @@
package org.http4s
package server

import cats.Monad
import cats.data.{Kleisli, OptionT}

object ContextMiddleware {
def apply[F[_]: Monad, T](
getContext: Kleisli[OptionT[F, ?], Request[F], T]): ContextMiddleware[F, T] =
_.compose(Kleisli((r: Request[F]) => getContext(r).map(ContextRequest(_, r))))
}
6 changes: 6 additions & 0 deletions server/src/main/scala/org/http4s/server/package.scala
Expand Up @@ -71,6 +71,12 @@ package object server {
type AuthMiddleware[F[_], T] =
Middleware[OptionT[F, ?], AuthedRequest[F, T], Response[F], Request[F], Response[F]]

/**
* An HTTP middleware that adds a context.
*/
type ContextMiddleware[F[_], T] =
Copy link
Member

Choose a reason for hiding this comment

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

Does anybody actually use these middleware aliases? I've come to regard them as obfuscation more than anything. I wonder what others think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are using them on our project. I find them quite useful.

Copy link
Member

Choose a reason for hiding this comment

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

We too use them.

Middleware[OptionT[F, ?], ContextRequest[F, T], Response[F], Request[F], Response[F]]

/**
* Old name for SSLConfig
*/
Expand Down
Expand Up @@ -18,7 +18,7 @@ class AuthMiddlewareSpec extends Http4sSpec {
Kleisli.pure(Left("Unauthorized"))

val onAuthFailure: AuthedRoutes[String, IO] =
Kleisli(req => OptionT.liftF(Forbidden(req.authInfo)))
Kleisli(req => OptionT.liftF(Forbidden(req.context)))

val authedRoutes: AuthedRoutes[User, IO] =
AuthedRoutes.of {
Expand All @@ -41,7 +41,7 @@ class AuthMiddlewareSpec extends Http4sSpec {
Kleisli.pure(Right(userId))

val onAuthFailure: AuthedRoutes[String, IO] =
Kleisli(req => OptionT.liftF(Forbidden(req.authInfo)))
Kleisli(req => OptionT.liftF(Forbidden(req.context)))

val authedRoutes: AuthedRoutes[User, IO] =
AuthedRoutes.of {
Expand All @@ -63,7 +63,7 @@ class AuthMiddlewareSpec extends Http4sSpec {
Kleisli.pure(Right(userId))

val onAuthFailure: AuthedRoutes[String, IO] =
Kleisli(req => OptionT.liftF(Forbidden(req.authInfo)))
Kleisli(req => OptionT.liftF(Forbidden(req.context)))

val authedRoutes: AuthedRoutes[User, IO] =
AuthedRoutes.of {
Expand Down