Skip to content

Commit

Permalink
wip: first try at providing inspectable routes with minimal changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jrudolph committed Feb 13, 2020
1 parent e8a1b79 commit 26d9bc2
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package akka.http.scaladsl.server

import akka.http.impl.util.AkkaSpecWithMaterializer
import Directives._
import DirectiveRoute.addByNameNullaryApply
import DynamicDirective._

class InspectableRouteSpec extends AkkaSpecWithMaterializer {
"Routes" should {
"be inspectable" should {
"routes from method directives" in {
val route = get { post { complete("ok") } }
route shouldBe an[InspectableRoute]
println(route)
}
"route alternatives with ~" in {
val route = get { complete("ok") } ~ post { complete("ok") }
Expand All @@ -23,6 +26,18 @@ class InspectableRouteSpec extends AkkaSpecWithMaterializer {
)
route shouldBe an[InspectableRoute]
}
"even for routes with extractions" in {
val route =
parameters("name").static { name =>
get {
dynamic { implicit exC =>
complete(s"Hello ${name: String}")
}
}
}
route shouldBe an[InspectableRoute]
println(route)
}
}
}
}
15 changes: 15 additions & 0 deletions akka-http/src/main/scala/akka/http/scaladsl/server/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._
import akka.http.impl.util._

final case class DirectiveMetaInformation(name: String)

// FIXME: in the best case we can move that into Directive.apply for less indirection
private class DirectiveWithChangedMetaInformation[L](original: Directive[L], _metaInformation: DirectiveMetaInformation)(implicit ev: Tuple[L]) extends Directive[L] {
override def tapply(f: L => Route): Route = original.tapply(f)
override def metaInformation: Option[DirectiveMetaInformation] = Some(_metaInformation)
override def withMetaInformation(newInformation: DirectiveMetaInformation): Directive[L] = new DirectiveWithChangedMetaInformation[L](original, newInformation)
}

/**
* A directive that provides a tuple of values of type `L` to create an inner route.
*/
Expand All @@ -27,6 +36,12 @@ abstract class Directive[L](implicit val ev: Tuple[L]) {
*/
def tapply(f: L => Route): Route
//#basic

def metaInformation: Option[DirectiveMetaInformation] = None
def withMetaInformation(newInformation: DirectiveMetaInformation): Directive[L] =
new DirectiveWithChangedMetaInformation[L](this, newInformation)
def named(name: String): Directive[L] = withMetaInformation(DirectiveMetaInformation(name))

/**
* Joins two directives into one which runs the second directive if the first one rejects.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,16 @@ object DirectiveRoute {
case x => Impl(x, child, directiveName)
}

implicit class AddMetadataToDirective0(directive: Directive0) {
def ^(directiveName: String): Directive0 =
Directive { innerCons =>
val inner = innerCons(())
val impl = directive.tapply(_ => inner)
wrap(impl, inner, directiveName)
}
}
implicit def addByNameNullaryApply(directive: Directive0): Route => Route =
inner => {
val impl = directive.tapply(_ => inner)
wrap(impl, inner, directive.metaInformation.fold("<unknown>")(_.name))
}

private final case class Impl(
implementation: Route,
child: Route,
directiveName: String) extends DirectiveRoute {
}
directiveName: String) extends DirectiveRoute
}

sealed trait ExtractionToken[+T]
Expand All @@ -74,5 +70,24 @@ sealed trait ExtractionContext {
def extract[T](token: ExtractionToken[T]): T
}
object DynamicDirective {
def dynamic: Directive1[ExtractionContext] = ???
import Directives._
def dynamic: Directive1[ExtractionContext] = extractRequestContext.flatMap { ctx =>
provide {
new ExtractionContext {
override def extract[T](token: ExtractionToken[T]): T = ctx.tokenValue(token)
}
}
}

implicit class AddStatic[T](d: Directive1[T]) {
def static: Directive1[ExtractionToken[T]] =
Directive { innerCons =>
val tok = new ExtractionToken[T] {}
val inner = innerCons(Tuple1(tok))
val real = d { t => ctx =>
inner(ctx.addTokenValue(tok, t))
}
DirectiveRoute.wrap(real, inner, d.metaInformation.fold("<unknown>")(_.name))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,7 @@ trait RequestContext {
* Removes a potentially existing Accept header from the request headers.
*/
def withAcceptAll: RequestContext

def tokenValue[T](token: ExtractionToken[T]): T
def addTokenValue[T](token: ExtractionToken[T], value: T): RequestContext
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@ import scala.concurrent.{ ExecutionContextExecutor, Future }
private[http] class RequestContextImpl(
val request: HttpRequest,
val unmatchedPath: Uri.Path,
val tokenValues: Map[ExtractionToken[_], Any],
val executionContext: ExecutionContextExecutor,
val materializer: Materializer,
val log: LoggingAdapter,
val settings: RoutingSettings,
val parserSettings: ParserSettings) extends RequestContext {

def this(request: HttpRequest, log: LoggingAdapter, settings: RoutingSettings, parserSettings: ParserSettings)(implicit ec: ExecutionContextExecutor, materializer: Materializer) =
this(request, request.uri.path, ec, materializer, log, settings, parserSettings)
this(request, request.uri.path, Map.empty, ec, materializer, log, settings, parserSettings)

def this(request: HttpRequest, log: LoggingAdapter, settings: RoutingSettings)(implicit ec: ExecutionContextExecutor, materializer: Materializer) =
this(request, request.uri.path, ec, materializer, log, settings, ParserSettings(ActorMaterializerHelper.downcast(materializer).system))
this(request, request.uri.path, Map.empty, ec, materializer, log, settings, ParserSettings(ActorMaterializerHelper.downcast(materializer).system))

def reconfigure(executionContext: ExecutionContextExecutor, materializer: Materializer, log: LoggingAdapter, settings: RoutingSettings): RequestContext =
copy(executionContext = executionContext, materializer = materializer, log = log, routingSettings = settings)
Expand Down Expand Up @@ -106,15 +107,19 @@ private[http] class RequestContextImpl(
case _ => this
}

override def tokenValue[T](token: ExtractionToken[T]): T = tokenValues(token).asInstanceOf[T]
override def addTokenValue[T](token: ExtractionToken[T], value: T): RequestContext = copy(tokenValues = tokenValues + (token -> value))

private def copy(
request: HttpRequest = request,
unmatchedPath: Uri.Path = unmatchedPath,
executionContext: ExecutionContextExecutor = executionContext,
materializer: Materializer = materializer,
log: LoggingAdapter = log,
routingSettings: RoutingSettings = settings,
parserSettings: ParserSettings = parserSettings) =
new RequestContextImpl(request, unmatchedPath, executionContext, materializer, log, routingSettings, parserSettings)
request: HttpRequest = request,
unmatchedPath: Uri.Path = unmatchedPath,
tokenValues: Map[ExtractionToken[_], Any] = tokenValues,
executionContext: ExecutionContextExecutor = executionContext,
materializer: Materializer = materializer,
log: LoggingAdapter = log,
routingSettings: RoutingSettings = settings,
parserSettings: ParserSettings = parserSettings) =
new RequestContextImpl(request, unmatchedPath, tokenValues, executionContext, materializer, log, routingSettings, parserSettings)

override def toString: String =
s"""RequestContext($request, $unmatchedPath, [more settings])"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package directives

import akka.http.scaladsl.model.{ StatusCodes, HttpMethod }
import akka.http.scaladsl.model.HttpMethods._
import DirectiveRoute.AddMetadataToDirective0

/**
* @groupname method Method directives
Expand Down Expand Up @@ -85,7 +84,7 @@ trait MethodDirectives {
extractMethod.flatMap[Unit] {
case `httpMethod` => pass
case _ => reject(MethodRejection(httpMethod))
} & cancelRejections(classOf[MethodRejection]) ^ s"method($httpMethod)"
} & cancelRejections(classOf[MethodRejection]) named s"method($httpMethod)"
//#method

/**
Expand Down Expand Up @@ -115,12 +114,12 @@ object MethodDirectives extends MethodDirectives {
BasicDirectives.extract(_.request.method)

// format: OFF
private val _delete : Directive0 = method(DELETE) ^ "delete"
private val _get : Directive0 = method(GET) ^ "get"
private val _head : Directive0 = method(HEAD) ^ "head"
private val _options: Directive0 = method(OPTIONS) ^ "options"
private val _patch : Directive0 = method(PATCH) ^ "patch"
private val _post : Directive0 = method(POST) ^ "post"
private val _put : Directive0 = method(PUT) ^ "put"
private val _delete : Directive0 = method(DELETE) named "delete"
private val _get : Directive0 = method(GET) named "get"
private val _head : Directive0 = method(HEAD) named "head"
private val _options: Directive0 = method(OPTIONS) named "options"
private val _patch : Directive0 = method(PATCH) named "patch"
private val _post : Directive0 = method(POST) named "post"
private val _put : Directive0 = method(PUT) named "put"
// format: ON
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ object ParameterDirectives extends ParameterDirectives {
def apply(): Out
}
object ParamMagnet {
implicit def apply[T](value: T)(implicit pdef: ParamDef[T]): ParamMagnet { type Out = pdef.Out } =
implicit def apply[T, O](value: T)(implicit pdef: ParamDef[T] { type Out = Directive[O] }): ParamMagnet { type Out = pdef.Out } =
new ParamMagnet {
type Out = pdef.Out
def apply() = pdef(value)
def apply() = pdef(value) named "parameters"
}
}

Expand Down

0 comments on commit 26d9bc2

Please sign in to comment.