htp #20103 introduce Scaladoc groups for Directives

This commit is contained in:
Lars Hupel 2016-04-04 11:50:44 +02:00 committed by Konrad Malawski
parent f2771eaab9
commit aec81c2ac2
28 changed files with 537 additions and 8 deletions

View file

@ -42,4 +42,4 @@ class NameDefaultUnmarshallerReceptacle[T](val name: String, val default: T, val
class RequiredValueUnmarshallerReceptacle[T](val name: String, val requiredValue: T, val um: FSU[T]) class RequiredValueUnmarshallerReceptacle[T](val name: String, val requiredValue: T, val um: FSU[T])
class RepeatedValueUnmarshallerReceptacle[T](val name: String, val um: FSU[T]) class RepeatedValueUnmarshallerReceptacle[T](val name: String, val um: FSU[T])

View file

@ -223,12 +223,18 @@ object PathMatcher extends ImplicitPathMatcherConstruction {
} }
} }
/**
* @groupname pathmatcherimpl Path matcher implicits
* @groupprio pathmatcherimpl 172
*/
trait ImplicitPathMatcherConstruction { trait ImplicitPathMatcherConstruction {
import PathMatcher._ import PathMatcher._
/** /**
* Creates a PathMatcher that consumes (a prefix of) the first path segment * Creates a PathMatcher that consumes (a prefix of) the first path segment
* (if the path begins with a segment) and extracts a given value. * (if the path begins with a segment) and extracts a given value.
*
* @group pathmatcherimpl
*/ */
implicit def stringExtractionPair2PathMatcher[T](tuple: (String, T)): PathMatcher1[T] = implicit def stringExtractionPair2PathMatcher[T](tuple: (String, T)): PathMatcher1[T] =
PathMatcher(tuple._1 :: Path.Empty, Tuple1(tuple._2)) PathMatcher(tuple._1 :: Path.Empty, Tuple1(tuple._2))
@ -236,10 +242,15 @@ trait ImplicitPathMatcherConstruction {
/** /**
* Creates a PathMatcher that consumes (a prefix of) the first path segment * Creates a PathMatcher that consumes (a prefix of) the first path segment
* (if the path begins with a segment). * (if the path begins with a segment).
*
* @group pathmatcherimpl
*/ */
implicit def segmentStringToPathMatcher(segment: String): PathMatcher0 = implicit def segmentStringToPathMatcher(segment: String): PathMatcher0 =
PathMatcher(segment :: Path.Empty, ()) PathMatcher(segment :: Path.Empty, ())
/**
* @group pathmatcherimpl
*/
implicit def stringNameOptionReceptacle2PathMatcher(nr: NameOptionReceptacle[String]): PathMatcher0 = implicit def stringNameOptionReceptacle2PathMatcher(nr: NameOptionReceptacle[String]): PathMatcher0 =
PathMatcher(nr.name).? PathMatcher(nr.name).?
@ -249,6 +260,8 @@ trait ImplicitPathMatcherConstruction {
* Extracts either the complete match (if the regex doesn't contain a capture group) or * Extracts either the complete match (if the regex doesn't contain a capture group) or
* the capture group (if the regex contains exactly one). * the capture group (if the regex contains exactly one).
* If the regex contains more than one capture group the method throws an IllegalArgumentException. * If the regex contains more than one capture group the method throws an IllegalArgumentException.
*
* @group pathmatcherimpl
*/ */
implicit def regex2PathMatcher(regex: Regex): PathMatcher1[String] = regex.groupCount match { implicit def regex2PathMatcher(regex: Regex): PathMatcher1[String] = regex.groupCount match {
case 0 new PathMatcher1[String] { case 0 new PathMatcher1[String] {
@ -276,18 +289,26 @@ trait ImplicitPathMatcherConstruction {
* Creates a PathMatcher from the given Map of path segments (prefixes) to extracted values. * Creates a PathMatcher from the given Map of path segments (prefixes) to extracted values.
* If the unmatched path starts with a segment having one of the maps keys as a prefix * If the unmatched path starts with a segment having one of the maps keys as a prefix
* the matcher consumes this path segment (prefix) and extracts the corresponding map value. * the matcher consumes this path segment (prefix) and extracts the corresponding map value.
*
* @group pathmatcherimpl
*/ */
implicit def valueMap2PathMatcher[T](valueMap: Map[String, T]): PathMatcher1[T] = implicit def valueMap2PathMatcher[T](valueMap: Map[String, T]): PathMatcher1[T] =
if (valueMap.isEmpty) PathMatchers.nothingMatcher if (valueMap.isEmpty) PathMatchers.nothingMatcher
else valueMap.map { case (prefix, value) stringExtractionPair2PathMatcher((prefix, value)) }.reduceLeft(_ | _) else valueMap.map { case (prefix, value) stringExtractionPair2PathMatcher((prefix, value)) }.reduceLeft(_ | _)
} }
/**
* @groupname pathmatcher Path matchers
* @groupprio pathmatcher 171
*/
trait PathMatchers { trait PathMatchers {
import PathMatcher._ import PathMatcher._
/** /**
* Converts a path string containing slashes into a PathMatcher that interprets slashes as * Converts a path string containing slashes into a PathMatcher that interprets slashes as
* path segment separators. * path segment separators.
*
* @group pathmatcher
*/ */
def separateOnSlashes(string: String): PathMatcher0 = { def separateOnSlashes(string: String): PathMatcher0 = {
@tailrec def split(ix: Int = 0, matcher: PathMatcher0 = null): PathMatcher0 = { @tailrec def split(ix: Int = 0, matcher: PathMatcher0 = null): PathMatcher0 = {
@ -301,6 +322,8 @@ trait PathMatchers {
/** /**
* A PathMatcher that matches a single slash character ('/'). * A PathMatcher that matches a single slash character ('/').
*
* @group pathmatcher
*/ */
object Slash extends PathMatcher0 { object Slash extends PathMatcher0 {
def apply(path: Path) = path match { def apply(path: Path) = path match {
@ -311,6 +334,8 @@ trait PathMatchers {
/** /**
* A PathMatcher that matches the very end of the requests URI path. * A PathMatcher that matches the very end of the requests URI path.
*
* @group pathmatcher
*/ */
object PathEnd extends PathMatcher0 { object PathEnd extends PathMatcher0 {
def apply(path: Path) = path match { def apply(path: Path) = path match {
@ -324,6 +349,8 @@ trait PathMatchers {
* unmatched part of the request's URI path as an (encoded!) String. * unmatched part of the request's URI path as an (encoded!) String.
* If you need access to the remaining unencoded elements of the path * If you need access to the remaining unencoded elements of the path
* use the `RestPath` matcher! * use the `RestPath` matcher!
*
* @group pathmatcher
*/ */
object Rest extends PathMatcher1[String] { object Rest extends PathMatcher1[String] {
def apply(path: Path) = Matched(Path.Empty, Tuple1(path.toString)) def apply(path: Path) = Matched(Path.Empty, Tuple1(path.toString))
@ -332,6 +359,8 @@ trait PathMatchers {
/** /**
* A PathMatcher that matches and extracts the complete remaining, * A PathMatcher that matches and extracts the complete remaining,
* unmatched part of the request's URI path. * unmatched part of the request's URI path.
*
* @group pathmatcher
*/ */
object RestPath extends PathMatcher1[Path] { object RestPath extends PathMatcher1[Path] {
def apply(path: Path) = Matched(Path.Empty, Tuple1(path)) def apply(path: Path) = Matched(Path.Empty, Tuple1(path))
@ -341,6 +370,8 @@ trait PathMatchers {
* A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Int value. * A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Int value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger * The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger
* than Int.MaxValue. * than Int.MaxValue.
*
* @group pathmatcher
*/ */
object IntNumber extends NumberMatcher[Int](Int.MaxValue, 10) { object IntNumber extends NumberMatcher[Int](Int.MaxValue, 10) {
def fromChar(c: Char) = fromDecimalChar(c) def fromChar(c: Char) = fromDecimalChar(c)
@ -350,6 +381,8 @@ trait PathMatchers {
* A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Long value. * A PathMatcher that efficiently matches a number of digits and extracts their (non-negative) Long value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger * The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger
* than Long.MaxValue. * than Long.MaxValue.
*
* @group pathmatcher
*/ */
object LongNumber extends NumberMatcher[Long](Long.MaxValue, 10) { object LongNumber extends NumberMatcher[Long](Long.MaxValue, 10) {
def fromChar(c: Char) = fromDecimalChar(c) def fromChar(c: Char) = fromDecimalChar(c)
@ -359,6 +392,8 @@ trait PathMatchers {
* A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Int value. * A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Int value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger * The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger
* than Int.MaxValue. * than Int.MaxValue.
*
* @group pathmatcher
*/ */
object HexIntNumber extends NumberMatcher[Int](Int.MaxValue, 16) { object HexIntNumber extends NumberMatcher[Int](Int.MaxValue, 16) {
def fromChar(c: Char) = fromHexChar(c) def fromChar(c: Char) = fromHexChar(c)
@ -368,12 +403,17 @@ trait PathMatchers {
* A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Long value. * A PathMatcher that efficiently matches a number of hex-digits and extracts their (non-negative) Long value.
* The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger * The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger
* than Long.MaxValue. * than Long.MaxValue.
*
* @group pathmatcher
*/ */
object HexLongNumber extends NumberMatcher[Long](Long.MaxValue, 16) { object HexLongNumber extends NumberMatcher[Long](Long.MaxValue, 16) {
def fromChar(c: Char) = fromHexChar(c) def fromChar(c: Char) = fromHexChar(c)
} }
// common implementation of Number matchers // common implementation of Number matchers
/**
* @group pathmatcher
*/
abstract class NumberMatcher[@specialized(Int, Long) T](max: T, base: T)(implicit x: Integral[T]) abstract class NumberMatcher[@specialized(Int, Long) T](max: T, base: T)(implicit x: Integral[T])
extends PathMatcher1[T] { extends PathMatcher1[T] {
@ -414,6 +454,8 @@ trait PathMatchers {
/** /**
* A PathMatcher that matches and extracts a Double value. The matched string representation is the pure decimal, * A PathMatcher that matches and extracts a Double value. The matched string representation is the pure decimal,
* optionally signed form of a double value, i.e. without exponent. * optionally signed form of a double value, i.e. without exponent.
*
* @group pathmatcher
*/ */
val DoubleNumber: PathMatcher1[Double] = val DoubleNumber: PathMatcher1[Double] =
PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string
@ -423,6 +465,8 @@ trait PathMatchers {
/** /**
* A PathMatcher that matches and extracts a java.util.UUID instance. * A PathMatcher that matches and extracts a java.util.UUID instance.
*
* @group pathmatcher
*/ */
val JavaUUID: PathMatcher1[UUID] = val JavaUUID: PathMatcher1[UUID] =
PathMatcher("""[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}""".r) flatMap { string PathMatcher("""[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}""".r) flatMap { string
@ -433,12 +477,16 @@ trait PathMatchers {
/** /**
* A PathMatcher that always matches, doesn't consume anything and extracts nothing. * A PathMatcher that always matches, doesn't consume anything and extracts nothing.
* Serves mainly as a neutral element in PathMatcher composition. * Serves mainly as a neutral element in PathMatcher composition.
*
* @group pathmatcher
*/ */
val Neutral: PathMatcher0 = PathMatcher.provide(()) val Neutral: PathMatcher0 = PathMatcher.provide(())
/** /**
* A PathMatcher that matches if the unmatched path starts with a path segment. * A PathMatcher that matches if the unmatched path starts with a path segment.
* If so the path segment is extracted as a String. * If so the path segment is extracted as a String.
*
* @group pathmatcher
*/ */
object Segment extends PathMatcher1[String] { object Segment extends PathMatcher1[String] {
def apply(path: Path) = path match { def apply(path: Path) = path match {
@ -451,6 +499,8 @@ trait PathMatchers {
* A PathMatcher that matches up to 128 remaining segments as a List[String]. * A PathMatcher that matches up to 128 remaining segments as a List[String].
* This can also be no segments resulting in the empty list. * This can also be no segments resulting in the empty list.
* If the path has a trailing slash this slash will *not* be matched. * If the path has a trailing slash this slash will *not* be matched.
*
* @group pathmatcher
*/ */
val Segments: PathMatcher1[List[String]] = Segments(min = 0, max = 128) val Segments: PathMatcher1[List[String]] = Segments(min = 0, max = 128)
@ -458,6 +508,8 @@ trait PathMatchers {
* A PathMatcher that matches the given number of path segments (separated by slashes) as a List[String]. * A PathMatcher that matches the given number of path segments (separated by slashes) as a List[String].
* If there are more than `count` segments present the remaining ones will be left unmatched. * If there are more than `count` segments present the remaining ones will be left unmatched.
* If the path has a trailing slash this slash will *not* be matched. * If the path has a trailing slash this slash will *not* be matched.
*
* @group pathmatcher
*/ */
def Segments(count: Int): PathMatcher1[List[String]] = Segment.repeat(count, separator = Slash) def Segments(count: Int): PathMatcher1[List[String]] = Segment.repeat(count, separator = Slash)
@ -465,11 +517,15 @@ trait PathMatchers {
* A PathMatcher that matches between `min` and `max` (both inclusively) path segments (separated by slashes) * A PathMatcher that matches between `min` and `max` (both inclusively) path segments (separated by slashes)
* as a List[String]. If there are more than `count` segments present the remaining ones will be left unmatched. * as a List[String]. If there are more than `count` segments present the remaining ones will be left unmatched.
* If the path has a trailing slash this slash will *not* be matched. * If the path has a trailing slash this slash will *not* be matched.
*
* @group pathmatcher
*/ */
def Segments(min: Int, max: Int): PathMatcher1[List[String]] = Segment.repeat(min, max, separator = Slash) def Segments(min: Int, max: Int): PathMatcher1[List[String]] = Segment.repeat(min, max, separator = Slash)
/** /**
* A PathMatcher that never matches anything. * A PathMatcher that never matches anything.
*
* @group pathmatcher
*/ */
def nothingMatcher[L: Tuple]: PathMatcher[L] = def nothingMatcher[L: Tuple]: PathMatcher[L] =
new PathMatcher[L] { new PathMatcher[L] {

View file

@ -7,8 +7,15 @@ package akka.http.scaladsl.server
import akka.http.scaladsl.util.FastFuture import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.util.FastFuture._
/**
* @groupname concat Route concatenation
* @groupprio concat 300
*/
trait RouteConcatenation { trait RouteConcatenation {
/**
* @group concat
*/
implicit def enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation = implicit def enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation =
new RouteConcatenation.RouteWithConcatenation(route: Route) new RouteConcatenation.RouteWithConcatenation(route: Route)
} }
@ -32,4 +39,4 @@ object RouteConcatenation extends RouteConcatenation {
} }
} }
} }
} }

View file

@ -15,75 +15,131 @@ import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.util.FastFuture._
/**
* @groupname basic Basic directives
* @groupprio basic 10
*/
trait BasicDirectives { trait BasicDirectives {
/**
* @group basic
*/
def mapInnerRoute(f: Route Route): Directive0 = def mapInnerRoute(f: Route Route): Directive0 =
Directive { inner f(inner(())) } Directive { inner f(inner(())) }
/**
* @group basic
*/
def mapRequestContext(f: RequestContext RequestContext): Directive0 = def mapRequestContext(f: RequestContext RequestContext): Directive0 =
mapInnerRoute { inner ctx inner(f(ctx)) } mapInnerRoute { inner ctx inner(f(ctx)) }
/**
* @group basic
*/
def mapRequest(f: HttpRequest HttpRequest): Directive0 = def mapRequest(f: HttpRequest HttpRequest): Directive0 =
mapRequestContext(_ mapRequest f) mapRequestContext(_ mapRequest f)
/**
* @group basic
*/
def mapRouteResultFuture(f: Future[RouteResult] Future[RouteResult]): Directive0 = def mapRouteResultFuture(f: Future[RouteResult] Future[RouteResult]): Directive0 =
Directive { inner ctx f(inner(())(ctx)) } Directive { inner ctx f(inner(())(ctx)) }
/**
* @group basic
*/
def mapRouteResult(f: RouteResult RouteResult): Directive0 = def mapRouteResult(f: RouteResult RouteResult): Directive0 =
Directive { inner ctx inner(())(ctx).fast.map(f)(ctx.executionContext) } Directive { inner ctx inner(())(ctx).fast.map(f)(ctx.executionContext) }
/**
* @group basic
*/
def mapRouteResultWith(f: RouteResult Future[RouteResult]): Directive0 = def mapRouteResultWith(f: RouteResult Future[RouteResult]): Directive0 =
Directive { inner ctx inner(())(ctx).fast.flatMap(f)(ctx.executionContext) } Directive { inner ctx inner(())(ctx).fast.flatMap(f)(ctx.executionContext) }
/**
* @group basic
*/
def mapRouteResultPF(f: PartialFunction[RouteResult, RouteResult]): Directive0 = def mapRouteResultPF(f: PartialFunction[RouteResult, RouteResult]): Directive0 =
mapRouteResult(f.applyOrElse(_, conforms[RouteResult])) mapRouteResult(f.applyOrElse(_, conforms[RouteResult]))
/**
* @group basic
*/
def mapRouteResultWithPF(f: PartialFunction[RouteResult, Future[RouteResult]]): Directive0 = def mapRouteResultWithPF(f: PartialFunction[RouteResult, Future[RouteResult]]): Directive0 =
mapRouteResultWith(f.applyOrElse(_, FastFuture.successful[RouteResult])) mapRouteResultWith(f.applyOrElse(_, FastFuture.successful[RouteResult]))
/**
* @group basic
*/
def recoverRejections(f: immutable.Seq[Rejection] RouteResult): Directive0 = def recoverRejections(f: immutable.Seq[Rejection] RouteResult): Directive0 =
mapRouteResultPF { case RouteResult.Rejected(rejections) f(rejections) } mapRouteResultPF { case RouteResult.Rejected(rejections) f(rejections) }
/**
* @group basic
*/
def recoverRejectionsWith(f: immutable.Seq[Rejection] Future[RouteResult]): Directive0 = def recoverRejectionsWith(f: immutable.Seq[Rejection] Future[RouteResult]): Directive0 =
mapRouteResultWithPF { case RouteResult.Rejected(rejections) f(rejections) } mapRouteResultWithPF { case RouteResult.Rejected(rejections) f(rejections) }
/**
* @group basic
*/
def mapRejections(f: immutable.Seq[Rejection] immutable.Seq[Rejection]): Directive0 = def mapRejections(f: immutable.Seq[Rejection] immutable.Seq[Rejection]): Directive0 =
recoverRejections(rejections RouteResult.Rejected(f(rejections))) recoverRejections(rejections RouteResult.Rejected(f(rejections)))
/**
* @group basic
*/
def mapResponse(f: HttpResponse HttpResponse): Directive0 = def mapResponse(f: HttpResponse HttpResponse): Directive0 =
mapRouteResultPF { case RouteResult.Complete(response) RouteResult.Complete(f(response)) } mapRouteResultPF { case RouteResult.Complete(response) RouteResult.Complete(f(response)) }
/**
* @group basic
*/
def mapResponseEntity(f: ResponseEntity ResponseEntity): Directive0 = def mapResponseEntity(f: ResponseEntity ResponseEntity): Directive0 =
mapResponse(_ mapEntity f) mapResponse(_ mapEntity f)
/**
* @group basic
*/
def mapResponseHeaders(f: immutable.Seq[HttpHeader] immutable.Seq[HttpHeader]): Directive0 = def mapResponseHeaders(f: immutable.Seq[HttpHeader] immutable.Seq[HttpHeader]): Directive0 =
mapResponse(_ mapHeaders f) mapResponse(_ mapHeaders f)
/** /**
* A Directive0 that always passes the request on to its inner route * A Directive0 that always passes the request on to its inner route
* (i.e. does nothing with the request or the response). * (i.e. does nothing with the request or the response).
*
* @group basic
*/ */
def pass: Directive0 = Directive.Empty def pass: Directive0 = Directive.Empty
/** /**
* Injects the given value into a directive. * Injects the given value into a directive.
*
* @group basic
*/ */
def provide[T](value: T): Directive1[T] = tprovide(Tuple1(value)) def provide[T](value: T): Directive1[T] = tprovide(Tuple1(value))
/** /**
* Injects the given values into a directive. * Injects the given values into a directive.
*
* @group basic
*/ */
def tprovide[L: Tuple](values: L): Directive[L] = def tprovide[L: Tuple](values: L): Directive[L] =
Directive { _(values) } Directive { _(values) }
/** /**
* Extracts a single value using the given function. * Extracts a single value using the given function.
*
* @group basic
*/ */
def extract[T](f: RequestContext T): Directive1[T] = def extract[T](f: RequestContext T): Directive1[T] =
textract(ctx Tuple1(f(ctx))) textract(ctx Tuple1(f(ctx)))
/** /**
* Extracts a number of values using the given function. * Extracts a number of values using the given function.
*
* @group basic
*/ */
def textract[L: Tuple](f: RequestContext L): Directive[L] = def textract[L: Tuple](f: RequestContext L): Directive[L] =
Directive { inner ctx inner(f(ctx))(ctx) } Directive { inner ctx inner(f(ctx))(ctx) }
@ -91,6 +147,8 @@ trait BasicDirectives {
/** /**
* Adds a TransformationRejection cancelling all rejections equal to the given one * Adds a TransformationRejection cancelling all rejections equal to the given one
* to the list of rejections potentially coming back from the inner route. * to the list of rejections potentially coming back from the inner route.
*
* @group basic
*/ */
def cancelRejection(rejection: Rejection): Directive0 = def cancelRejection(rejection: Rejection): Directive0 =
cancelRejections(_ == rejection) cancelRejections(_ == rejection)
@ -98,6 +156,8 @@ trait BasicDirectives {
/** /**
* Adds a TransformationRejection cancelling all rejections of one of the given classes * Adds a TransformationRejection cancelling all rejections of one of the given classes
* to the list of rejections potentially coming back from the inner route. * to the list of rejections potentially coming back from the inner route.
*
* @group basic
*/ */
def cancelRejections(classes: Class[_]*): Directive0 = def cancelRejections(classes: Class[_]*): Directive0 =
cancelRejections(r classes.exists(_ isInstance r)) cancelRejections(r classes.exists(_ isInstance r))
@ -105,91 +165,123 @@ trait BasicDirectives {
/** /**
* Adds a TransformationRejection cancelling all rejections for which the given filter function returns true * Adds a TransformationRejection cancelling all rejections for which the given filter function returns true
* to the list of rejections potentially coming back from the inner route. * to the list of rejections potentially coming back from the inner route.
*
* @group basic
*/ */
def cancelRejections(cancelFilter: Rejection Boolean): Directive0 = def cancelRejections(cancelFilter: Rejection Boolean): Directive0 =
mapRejections(_ :+ TransformationRejection(_ filterNot cancelFilter)) mapRejections(_ :+ TransformationRejection(_ filterNot cancelFilter))
/** /**
* Transforms the unmatchedPath of the RequestContext using the given function. * Transforms the unmatchedPath of the RequestContext using the given function.
*
* @group basic
*/ */
def mapUnmatchedPath(f: Uri.Path Uri.Path): Directive0 = def mapUnmatchedPath(f: Uri.Path Uri.Path): Directive0 =
mapRequestContext(_ mapUnmatchedPath f) mapRequestContext(_ mapUnmatchedPath f)
/** /**
* Extracts the yet unmatched path from the RequestContext. * Extracts the yet unmatched path from the RequestContext.
*
* @group basic
*/ */
def extractUnmatchedPath: Directive1[Uri.Path] = BasicDirectives._extractUnmatchedPath def extractUnmatchedPath: Directive1[Uri.Path] = BasicDirectives._extractUnmatchedPath
/** /**
* Extracts the current [[HttpRequest]] instance. * Extracts the current [[HttpRequest]] instance.
*
* @group basic
*/ */
def extractRequest: Directive1[HttpRequest] = BasicDirectives._extractRequest def extractRequest: Directive1[HttpRequest] = BasicDirectives._extractRequest
/** /**
* Extracts the complete request URI. * Extracts the complete request URI.
*
* @group basic
*/ */
def extractUri: Directive1[Uri] = BasicDirectives._extractUri def extractUri: Directive1[Uri] = BasicDirectives._extractUri
/** /**
* Runs its inner route with the given alternative [[scala.concurrent.ExecutionContextExecutor]]. * Runs its inner route with the given alternative [[scala.concurrent.ExecutionContextExecutor]].
*
* @group basic
*/ */
def withExecutionContext(ec: ExecutionContextExecutor): Directive0 = def withExecutionContext(ec: ExecutionContextExecutor): Directive0 =
mapRequestContext(_ withExecutionContext ec) mapRequestContext(_ withExecutionContext ec)
/** /**
* Extracts the [[scala.concurrent.ExecutionContextExecutor]] from the [[akka.http.scaladsl.server.RequestContext]]. * Extracts the [[scala.concurrent.ExecutionContextExecutor]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/ */
def extractExecutionContext: Directive1[ExecutionContextExecutor] = BasicDirectives._extractExecutionContext def extractExecutionContext: Directive1[ExecutionContextExecutor] = BasicDirectives._extractExecutionContext
/** /**
* Runs its inner route with the given alternative [[akka.stream.Materializer]]. * Runs its inner route with the given alternative [[akka.stream.Materializer]].
*
* @group basic
*/ */
def withMaterializer(materializer: Materializer): Directive0 = def withMaterializer(materializer: Materializer): Directive0 =
mapRequestContext(_ withMaterializer materializer) mapRequestContext(_ withMaterializer materializer)
/** /**
* Extracts the [[akka.stream.Materializer]] from the [[akka.http.scaladsl.server.RequestContext]]. * Extracts the [[akka.stream.Materializer]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/ */
def extractMaterializer: Directive1[Materializer] = BasicDirectives._extractMaterializer def extractMaterializer: Directive1[Materializer] = BasicDirectives._extractMaterializer
/** /**
* Runs its inner route with the given alternative [[akka.event.LoggingAdapter]]. * Runs its inner route with the given alternative [[akka.event.LoggingAdapter]].
*
* @group basic
*/ */
def withLog(log: LoggingAdapter): Directive0 = def withLog(log: LoggingAdapter): Directive0 =
mapRequestContext(_ withLog log) mapRequestContext(_ withLog log)
/** /**
* Extracts the [[akka.event.LoggingAdapter]] from the [[akka.http.scaladsl.server.RequestContext]]. * Extracts the [[akka.event.LoggingAdapter]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/ */
def extractLog: Directive1[LoggingAdapter] = def extractLog: Directive1[LoggingAdapter] =
BasicDirectives._extractLog BasicDirectives._extractLog
/** /**
* Runs its inner route with the given alternative [[RoutingSettings]]. * Runs its inner route with the given alternative [[RoutingSettings]].
*
* @group basic
*/ */
def withSettings(settings: RoutingSettings): Directive0 = def withSettings(settings: RoutingSettings): Directive0 =
mapRequestContext(_ withRoutingSettings settings) mapRequestContext(_ withRoutingSettings settings)
/** /**
* Runs the inner route with settings mapped by the given function. * Runs the inner route with settings mapped by the given function.
*
* @group basic
*/ */
def mapSettings(f: RoutingSettings RoutingSettings): Directive0 = def mapSettings(f: RoutingSettings RoutingSettings): Directive0 =
mapRequestContext(ctx ctx.withRoutingSettings(f(ctx.settings))) mapRequestContext(ctx ctx.withRoutingSettings(f(ctx.settings)))
/** /**
* Extracts the [[RoutingSettings]] from the [[akka.http.scaladsl.server.RequestContext]]. * Extracts the [[RoutingSettings]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/ */
def extractSettings: Directive1[RoutingSettings] = def extractSettings: Directive1[RoutingSettings] =
BasicDirectives._extractSettings BasicDirectives._extractSettings
/** /**
* Extracts the [[akka.http.scaladsl.settings.ParserSettings]] from the [[akka.http.scaladsl.server.RequestContext]]. * Extracts the [[akka.http.scaladsl.settings.ParserSettings]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/ */
def extractParserSettings: Directive1[ParserSettings] = def extractParserSettings: Directive1[ParserSettings] =
BasicDirectives._extractParserSettings BasicDirectives._extractParserSettings
/** /**
* Extracts the [[akka.http.scaladsl.server.RequestContext]] itself. * Extracts the [[akka.http.scaladsl.server.RequestContext]] itself.
*
* @group basic
*/ */
def extractRequestContext: Directive1[RequestContext] = BasicDirectives._extractRequestContext def extractRequestContext: Directive1[RequestContext] = BasicDirectives._extractRequestContext
} }

View file

@ -12,6 +12,10 @@ import HttpMethods._
import StatusCodes._ import StatusCodes._
import EntityTag._ import EntityTag._
/**
* @groupname cachecondition Cache condition directives
* @groupprio cachecondition 20
*/
trait CacheConditionDirectives { trait CacheConditionDirectives {
import BasicDirectives._ import BasicDirectives._
import RouteDirectives._ import RouteDirectives._
@ -26,6 +30,8 @@ trait CacheConditionDirectives {
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly. * must be on a deeper level in your route structure in order to function correctly.
*
* @group cachecondition
*/ */
def conditional(eTag: EntityTag): Directive0 = conditional(Some(eTag), None) def conditional(eTag: EntityTag): Directive0 = conditional(Some(eTag), None)
@ -39,6 +45,8 @@ trait CacheConditionDirectives {
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly. * must be on a deeper level in your route structure in order to function correctly.
*
* @group cachecondition
*/ */
def conditional(lastModified: DateTime): Directive0 = conditional(None, Some(lastModified)) def conditional(lastModified: DateTime): Directive0 = conditional(None, Some(lastModified))
@ -52,6 +60,8 @@ trait CacheConditionDirectives {
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly. * must be on a deeper level in your route structure in order to function correctly.
*
* @group cachecondition
*/ */
def conditional(eTag: EntityTag, lastModified: DateTime): Directive0 = conditional(Some(eTag), Some(lastModified)) def conditional(eTag: EntityTag, lastModified: DateTime): Directive0 = conditional(Some(eTag), Some(lastModified))
@ -65,6 +75,8 @@ trait CacheConditionDirectives {
* Note: if you want to combine this directive with `withRangeSupport(...)` you need to put * Note: if you want to combine this directive with `withRangeSupport(...)` you need to put
* it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)` * it on the *outside* of the `withRangeSupport(...)` directive, i.e. `withRangeSupport(...)`
* must be on a deeper level in your route structure in order to function correctly. * must be on a deeper level in your route structure in order to function correctly.
*
* @group cachecondition
*/ */
def conditional(eTag: Option[EntityTag], lastModified: Option[DateTime]): Directive0 = { def conditional(eTag: Option[EntityTag], lastModified: Option[DateTime]): Directive0 = {
def addResponseHeaders: Directive0 = def addResponseHeaders: Directive0 =

View file

@ -12,6 +12,10 @@ import akka.http.scaladsl.model._
import akka.http.scaladsl.coding._ import akka.http.scaladsl.coding._
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname coding Coding directives
* @groupprio coding 50
*/
trait CodingDirectives { trait CodingDirectives {
import BasicDirectives._ import BasicDirectives._
import MiscDirectives._ import MiscDirectives._
@ -23,6 +27,8 @@ trait CodingDirectives {
/** /**
* Rejects the request with an UnacceptedResponseEncodingRejection * Rejects the request with an UnacceptedResponseEncodingRejection
* if the given response encoding is not accepted by the client. * if the given response encoding is not accepted by the client.
*
* @group coding
*/ */
def responseEncodingAccepted(encoding: HttpEncoding): Directive0 = def responseEncodingAccepted(encoding: HttpEncoding): Directive0 =
extractRequest.flatMap { request extractRequest.flatMap { request
@ -37,6 +43,8 @@ trait CodingDirectives {
* *
* If the `Accept-Encoding` header is missing or empty or specifies an encoding other than * If the `Accept-Encoding` header is missing or empty or specifies an encoding other than
* identity, gzip or deflate then no encoding is used. * identity, gzip or deflate then no encoding is used.
*
* @group coding
*/ */
def encodeResponse: Directive0 = def encodeResponse: Directive0 =
_encodeResponse(DefaultEncodeResponseEncoders) _encodeResponse(DefaultEncodeResponseEncoders)
@ -51,6 +59,8 @@ trait CodingDirectives {
* *
* If the `Accept-Encoding` header is empty and `NoCoding` is part of the encoders then no * If the `Accept-Encoding` header is empty and `NoCoding` is part of the encoders then no
* response encoding is used. Otherwise the request is rejected. * response encoding is used. Otherwise the request is rejected.
*
* @group coding
*/ */
def encodeResponseWith(first: Encoder, more: Encoder*): Directive0 = def encodeResponseWith(first: Encoder, more: Encoder*): Directive0 =
_encodeResponse(immutable.Seq(first +: more: _*)) _encodeResponse(immutable.Seq(first +: more: _*))
@ -60,6 +70,8 @@ trait CodingDirectives {
/** /**
* Decodes the incoming request using the given Decoder. * Decodes the incoming request using the given Decoder.
* If the request encoding doesn't match the request is rejected with an `UnsupportedRequestEncodingRejection`. * If the request encoding doesn't match the request is rejected with an `UnsupportedRequestEncodingRejection`.
*
* @group coding
*/ */
def decodeRequestWith(decoder: Decoder): Directive0 = { def decodeRequestWith(decoder: Decoder): Directive0 = {
def applyDecoder = def applyDecoder =
@ -85,6 +97,8 @@ trait CodingDirectives {
/** /**
* Rejects the request with an UnsupportedRequestEncodingRejection if its encoding doesn't match the given one. * Rejects the request with an UnsupportedRequestEncodingRejection if its encoding doesn't match the given one.
*
* @group coding
*/ */
def requestEncodedWith(encoding: HttpEncoding): Directive0 = def requestEncodedWith(encoding: HttpEncoding): Directive0 =
extract(_.request.encoding).flatMap { extract(_.request.encoding).flatMap {
@ -97,6 +111,8 @@ trait CodingDirectives {
* encoders. If the request encoding doesn't match one of the given encoders * encoders. If the request encoding doesn't match one of the given encoders
* the request is rejected with an `UnsupportedRequestEncodingRejection`. * the request is rejected with an `UnsupportedRequestEncodingRejection`.
* If no decoders are given the default encoders (`Gzip`, `Deflate`, `NoCoding`) are used. * If no decoders are given the default encoders (`Gzip`, `Deflate`, `NoCoding`) are used.
*
* @group coding
*/ */
def decodeRequestWith(decoders: Decoder*): Directive0 = def decodeRequestWith(decoders: Decoder*): Directive0 =
theseOrDefault(decoders).map(decodeRequestWith).reduce(_ | _) theseOrDefault(decoders).map(decodeRequestWith).reduce(_ | _)
@ -105,6 +121,8 @@ trait CodingDirectives {
* Decompresses the incoming request if it is `gzip` or `deflate` compressed. * Decompresses the incoming request if it is `gzip` or `deflate` compressed.
* Uncompressed requests are passed through untouched. * Uncompressed requests are passed through untouched.
* If the request encoded with another encoding the request is rejected with an `UnsupportedRequestEncodingRejection`. * If the request encoded with another encoding the request is rejected with an `UnsupportedRequestEncodingRejection`.
*
* @group coding
*/ */
def decodeRequest: Directive0 = def decodeRequest: Directive0 =
decodeRequestWith(DefaultCoders: _*) decodeRequestWith(DefaultCoders: _*)
@ -112,6 +130,8 @@ trait CodingDirectives {
/** /**
* Inspects the response entity and adds a `Content-Encoding: gzip` response header if * Inspects the response entity and adds a `Content-Encoding: gzip` response header if
* the entities media-type is precompressed with gzip and no `Content-Encoding` header is present yet. * the entities media-type is precompressed with gzip and no `Content-Encoding` header is present yet.
*
* @group coding
*/ */
def withPrecompressedMediaTypeSupport: Directive0 = def withPrecompressedMediaTypeSupport: Directive0 =
mapResponse { response mapResponse { response

View file

@ -9,6 +9,10 @@ import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._ import akka.http.scaladsl.model.headers._
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname cookie Cookie directives
* @groupprio cookie 30
*/
trait CookieDirectives { trait CookieDirectives {
import HeaderDirectives._ import HeaderDirectives._
import RespondWithDirectives._ import RespondWithDirectives._
@ -17,6 +21,8 @@ trait CookieDirectives {
/** /**
* Extracts the [[HttpCookiePair]] with the given name. If the cookie is not present the * Extracts the [[HttpCookiePair]] with the given name. If the cookie is not present the
* request is rejected with a respective [[MissingCookieRejection]]. * request is rejected with a respective [[MissingCookieRejection]].
*
* @group cookie
*/ */
def cookie(name: String): Directive1[HttpCookiePair] = def cookie(name: String): Directive1[HttpCookiePair] =
headerValue(findCookie(name)) | reject(MissingCookieRejection(name)) headerValue(findCookie(name)) | reject(MissingCookieRejection(name))
@ -24,6 +30,8 @@ trait CookieDirectives {
/** /**
* Extracts the [[HttpCookiePair]] with the given name as an `Option[HttpCookiePair]`. * Extracts the [[HttpCookiePair]] with the given name as an `Option[HttpCookiePair]`.
* If the cookie is not present a value of `None` is extracted. * If the cookie is not present a value of `None` is extracted.
*
* @group cookie
*/ */
def optionalCookie(name: String): Directive1[Option[HttpCookiePair]] = def optionalCookie(name: String): Directive1[Option[HttpCookiePair]] =
optionalHeaderValue(findCookie(name)) optionalHeaderValue(findCookie(name))
@ -35,12 +43,16 @@ trait CookieDirectives {
/** /**
* Adds a [[Set-Cookie]] response header with the given cookies. * Adds a [[Set-Cookie]] response header with the given cookies.
*
* @group cookie
*/ */
def setCookie(first: HttpCookie, more: HttpCookie*): Directive0 = def setCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
respondWithHeaders((first :: more.toList).map(`Set-Cookie`(_))) respondWithHeaders((first :: more.toList).map(`Set-Cookie`(_)))
/** /**
* Adds a [[Set-Cookie]] response header expiring the given cookies. * Adds a [[Set-Cookie]] response header expiring the given cookies.
*
* @group cookie
*/ */
def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 = def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
respondWithHeaders((first :: more.toList).map { c respondWithHeaders((first :: more.toList).map { c
@ -49,6 +61,8 @@ trait CookieDirectives {
/** /**
* Adds a [[Set-Cookie]] response header expiring the cookie with the given properties. * Adds a [[Set-Cookie]] response header expiring the cookie with the given properties.
*
* @group cookie
*/ */
def deleteCookie(name: String, domain: String = "", path: String = ""): Directive0 = def deleteCookie(name: String, domain: String = "", path: String = ""): Directive0 =
deleteCookie(HttpCookie(name, "", domain = domain.toOption, path = path.toOption)) deleteCookie(HttpCookie(name, "", domain = domain.toOption, path = path.toOption))

View file

@ -9,11 +9,17 @@ import akka.event.Logging._
import akka.event.LoggingAdapter import akka.event.LoggingAdapter
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
/**
* @groupname debugging Debugging directives
* @groupprio debugging 40
*/
trait DebuggingDirectives { trait DebuggingDirectives {
import BasicDirectives._ import BasicDirectives._
/** /**
* Produces a log entry for every incoming request. * Produces a log entry for every incoming request.
*
* @group debugging
*/ */
def logRequest(magnet: LoggingMagnet[HttpRequest Unit]): Directive0 = def logRequest(magnet: LoggingMagnet[HttpRequest Unit]): Directive0 =
extractRequestContext.flatMap { ctx extractRequestContext.flatMap { ctx
@ -23,6 +29,8 @@ trait DebuggingDirectives {
/** /**
* Produces a log entry for every [[RouteResult]]. * Produces a log entry for every [[RouteResult]].
*
* @group debugging
*/ */
def logResult(magnet: LoggingMagnet[RouteResult Unit]): Directive0 = def logResult(magnet: LoggingMagnet[RouteResult Unit]): Directive0 =
extractRequestContext.flatMap { ctx extractRequestContext.flatMap { ctx
@ -34,6 +42,8 @@ trait DebuggingDirectives {
/** /**
* Produces a log entry for every incoming request and [[RouteResult]]. * Produces a log entry for every incoming request and [[RouteResult]].
*
* @group debugging
*/ */
def logRequestResult(magnet: LoggingMagnet[HttpRequest RouteResult Unit]): Directive0 = def logRequestResult(magnet: LoggingMagnet[HttpRequest RouteResult Unit]): Directive0 =
extractRequestContext.flatMap { ctx extractRequestContext.flatMap { ctx

View file

@ -11,12 +11,18 @@ import scala.util.control.NonFatal
import akka.http.scaladsl.util.FastFuture import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.util.FastFuture._ import akka.http.scaladsl.util.FastFuture._
/**
* @groupname execution Execution directives
* @groupprio execution 60
*/
trait ExecutionDirectives { trait ExecutionDirectives {
import BasicDirectives._ import BasicDirectives._
/** /**
* Transforms exceptions thrown during evaluation of its inner route using the given * Transforms exceptions thrown during evaluation of its inner route using the given
* [[akka.http.scaladsl.server.ExceptionHandler]]. * [[akka.http.scaladsl.server.ExceptionHandler]].
*
* @group execution
*/ */
def handleExceptions(handler: ExceptionHandler): Directive0 = def handleExceptions(handler: ExceptionHandler): Directive0 =
Directive { innerRouteBuilder Directive { innerRouteBuilder
@ -33,6 +39,8 @@ trait ExecutionDirectives {
/** /**
* Transforms rejections produced by its inner route using the given * Transforms rejections produced by its inner route using the given
* [[akka.http.scaladsl.server.RejectionHandler]]. * [[akka.http.scaladsl.server.RejectionHandler]].
*
* @group execution
*/ */
def handleRejections(handler: RejectionHandler): Directive0 = def handleRejections(handler: RejectionHandler): Directive0 =
extractRequestContext flatMap { ctx extractRequestContext flatMap { ctx
@ -55,4 +63,4 @@ trait ExecutionDirectives {
} }
} }
object ExecutionDirectives extends ExecutionDirectives object ExecutionDirectives extends ExecutionDirectives

View file

@ -19,6 +19,10 @@ import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._ import akka.http.scaladsl.model.headers._
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname fileandresource File and resource directives
* @groupprio fileandresource 70
*/
trait FileAndResourceDirectives { trait FileAndResourceDirectives {
import CacheConditionDirectives._ import CacheConditionDirectives._
import MethodDirectives._ import MethodDirectives._
@ -30,6 +34,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with the content of the given file. * Completes GET requests with the content of the given file.
* If the file cannot be found or read the request is rejected. * If the file cannot be found or read the request is rejected.
*
* @group fileandresource
*/ */
def getFromFile(fileName: String)(implicit resolver: ContentTypeResolver): Route = def getFromFile(fileName: String)(implicit resolver: ContentTypeResolver): Route =
getFromFile(new File(fileName)) getFromFile(new File(fileName))
@ -37,6 +43,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with the content of the given file. * Completes GET requests with the content of the given file.
* If the file cannot be found or read the request is rejected. * If the file cannot be found or read the request is rejected.
*
* @group fileandresource
*/ */
def getFromFile(file: File)(implicit resolver: ContentTypeResolver): Route = def getFromFile(file: File)(implicit resolver: ContentTypeResolver): Route =
getFromFile(file, resolver(file.getName)) getFromFile(file, resolver(file.getName))
@ -44,6 +52,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with the content of the given file. * Completes GET requests with the content of the given file.
* If the file cannot be found or read the request is rejected. * If the file cannot be found or read the request is rejected.
*
* @group fileandresource
*/ */
def getFromFile(file: File, contentType: ContentType): Route = def getFromFile(file: File, contentType: ContentType): Route =
get { get {
@ -72,6 +82,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with the content of the given class-path resource. * Completes GET requests with the content of the given class-path resource.
* If the resource cannot be found or read the Route rejects the request. * If the resource cannot be found or read the Route rejects the request.
*
* @group fileandresource
*/ */
def getFromResource(resourceName: String)(implicit resolver: ContentTypeResolver): Route = def getFromResource(resourceName: String)(implicit resolver: ContentTypeResolver): Route =
getFromResource(resourceName, resolver(resourceName)) getFromResource(resourceName, resolver(resourceName))
@ -79,6 +91,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with the content of the given resource. * Completes GET requests with the content of the given resource.
* If the resource is a directory or cannot be found or read the Route rejects the request. * If the resource is a directory or cannot be found or read the Route rejects the request.
*
* @group fileandresource
*/ */
def getFromResource(resourceName: String, contentType: ContentType, classLoader: ClassLoader = defaultClassLoader): Route = def getFromResource(resourceName: String, contentType: ContentType, classLoader: ClassLoader = defaultClassLoader): Route =
if (!resourceName.endsWith("/")) if (!resourceName.endsWith("/"))
@ -104,6 +118,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with the content of a file underneath the given directory. * Completes GET requests with the content of a file underneath the given directory.
* If the file cannot be read the Route rejects the request. * If the file cannot be read the Route rejects the request.
*
* @group fileandresource
*/ */
def getFromDirectory(directoryName: String)(implicit resolver: ContentTypeResolver): Route = { def getFromDirectory(directoryName: String)(implicit resolver: ContentTypeResolver): Route = {
val base = withTrailingSlash(directoryName) val base = withTrailingSlash(directoryName)
@ -120,6 +136,8 @@ trait FileAndResourceDirectives {
/** /**
* Completes GET requests with a unified listing of the contents of all given directories. * Completes GET requests with a unified listing of the contents of all given directories.
* The actual rendering of the directory contents is performed by the in-scope `Marshaller[DirectoryListing]`. * The actual rendering of the directory contents is performed by the in-scope `Marshaller[DirectoryListing]`.
*
* @group fileandresource
*/ */
def listDirectoryContents(directories: String*)(implicit renderer: DirectoryRenderer): Route = def listDirectoryContents(directories: String*)(implicit renderer: DirectoryRenderer): Route =
get { get {
@ -147,6 +165,8 @@ trait FileAndResourceDirectives {
/** /**
* Same as `getFromBrowseableDirectories` with only one directory. * Same as `getFromBrowseableDirectories` with only one directory.
*
* @group fileandresource
*/ */
def getFromBrowseableDirectory(directory: String)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = def getFromBrowseableDirectory(directory: String)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route =
getFromBrowseableDirectories(directory) getFromBrowseableDirectories(directory)
@ -154,6 +174,8 @@ trait FileAndResourceDirectives {
/** /**
* Serves the content of the given directories as a file system browser, i.e. files are sent and directories * Serves the content of the given directories as a file system browser, i.e. files are sent and directories
* served as browseable listings. * served as browseable listings.
*
* @group fileandresource
*/ */
def getFromBrowseableDirectories(directories: String*)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = { def getFromBrowseableDirectories(directories: String*)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = {
directories.map(getFromDirectory).reduceLeft(_ ~ _) ~ listDirectoryContents(directories: _*) directories.map(getFromDirectory).reduceLeft(_ ~ _) ~ listDirectoryContents(directories: _*)
@ -163,6 +185,8 @@ trait FileAndResourceDirectives {
* Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a * Same as "getFromDirectory" except that the file is not fetched from the file system but rather from a
* "resource directory". * "resource directory".
* If the requested resource is itself a directory or cannot be found or read the Route rejects the request. * If the requested resource is itself a directory or cannot be found or read the Route rejects the request.
*
* @group fileandresource
*/ */
def getFromResourceDirectory(directoryName: String, classLoader: ClassLoader = defaultClassLoader)(implicit resolver: ContentTypeResolver): Route = { def getFromResourceDirectory(directoryName: String, classLoader: ClassLoader = defaultClassLoader)(implicit resolver: ContentTypeResolver): Route = {
val base = if (directoryName.isEmpty) "" else withTrailingSlash(directoryName) val base = if (directoryName.isEmpty) "" else withTrailingSlash(directoryName)

View file

@ -12,6 +12,10 @@ import scala.concurrent.Future
import scala.util.{ Failure, Success } import scala.util.{ Failure, Success }
import akka.stream.scaladsl._ import akka.stream.scaladsl._
/**
* @groupname fileupload File upload directives
* @groupprio fileupload 80
*/
trait FileUploadDirectives { trait FileUploadDirectives {
import BasicDirectives._ import BasicDirectives._
@ -24,6 +28,8 @@ trait FileUploadDirectives {
* If there is an error writing to disk the request will be failed with the thrown exception, if there is no such * If there is an error writing to disk the request will be failed with the thrown exception, if there is no such
* field the request will be rejected, if there are multiple file parts with the same name, the first one will be * field the request will be rejected, if there are multiple file parts with the same name, the first one will be
* used and the subsequent ones ignored. * used and the subsequent ones ignored.
*
* @group fileupload
*/ */
def uploadedFile(fieldName: String): Directive1[(FileInfo, File)] = def uploadedFile(fieldName: String): Directive1[(FileInfo, File)] =
extractRequestContext.flatMap { ctx extractRequestContext.flatMap { ctx
@ -55,6 +61,8 @@ trait FileUploadDirectives {
* for streaming the file contents somewhere. If there is no such field the request will be rejected, * for streaming the file contents somewhere. If there is no such field the request will be rejected,
* if there are multiple file parts with the same name, the first one will be used and the subsequent * if there are multiple file parts with the same name, the first one will be used and the subsequent
* ones ignored. * ones ignored.
*
* @group fileupload
*/ */
def fileUpload(fieldName: String): Directive1[(FileInfo, Source[ByteString, Any])] = def fileUpload(fieldName: String): Directive1[(FileInfo, Source[ByteString, Any])] =
entity(as[Multipart.FormData]).flatMap { formData entity(as[Multipart.FormData]).flatMap { formData

View file

@ -16,33 +16,47 @@ import scala.collection.immutable
import scala.concurrent.Future import scala.concurrent.Future
import scala.util.{ Failure, Success } import scala.util.{ Failure, Success }
/**
* @groupname form Form field directives
* @groupprio form 90
*/
trait FormFieldDirectives extends ToNameReceptacleEnhancements { trait FormFieldDirectives extends ToNameReceptacleEnhancements {
import FormFieldDirectives._ import FormFieldDirectives._
/** /**
* Extracts HTTP form fields from the request as a ``Map[String, String]``. * Extracts HTTP form fields from the request as a ``Map[String, String]``.
*
* @group form
*/ */
def formFieldMap: Directive1[Map[String, String]] = _formFieldMap def formFieldMap: Directive1[Map[String, String]] = _formFieldMap
/** /**
* Extracts HTTP form fields from the request as a ``Map[String, List[String]]``. * Extracts HTTP form fields from the request as a ``Map[String, List[String]]``.
*
* @group form
*/ */
def formFieldMultiMap: Directive1[Map[String, List[String]]] = _formFieldMultiMap def formFieldMultiMap: Directive1[Map[String, List[String]]] = _formFieldMultiMap
/** /**
* Extracts HTTP form fields from the request as a ``Seq[(String, String)]``. * Extracts HTTP form fields from the request as a ``Seq[(String, String)]``.
*
* @group form
*/ */
def formFieldSeq: Directive1[immutable.Seq[(String, String)]] = _formFieldSeq def formFieldSeq: Directive1[immutable.Seq[(String, String)]] = _formFieldSeq
/** /**
* Extracts an HTTP form field from the request. * Extracts an HTTP form field from the request.
* Rejects the request if the defined form field matcher(s) don't match. * Rejects the request if the defined form field matcher(s) don't match.
*
* @group form
*/ */
def formField(pdm: FieldMagnet): pdm.Out = pdm() def formField(pdm: FieldMagnet): pdm.Out = pdm()
/** /**
* Extracts a number of HTTP form field from the request. * Extracts a number of HTTP form field from the request.
* Rejects the request if the defined form field matcher(s) don't match. * Rejects the request if the defined form field matcher(s) don't match.
*
* @group form
*/ */
def formFields(pdm: FieldMagnet): pdm.Out = pdm() def formFields(pdm: FieldMagnet): pdm.Out = pdm()

View file

@ -13,11 +13,17 @@ import akka.http.scaladsl.util.FastFuture._
// format: OFF // format: OFF
/**
* @groupname future Future directives
* @groupprio future 100
*/
trait FutureDirectives { trait FutureDirectives {
/** /**
* "Unwraps" a `Future[T]` and runs the inner route after future * "Unwraps" a `Future[T]` and runs the inner route after future
* completion with the future's value as an extraction of type `Try[T]`. * completion with the future's value as an extraction of type `Try[T]`.
*
* @group future
*/ */
def onComplete[T](future: Future[T]): Directive1[Try[T]] = def onComplete[T](future: Future[T]): Directive1[Try[T]] =
Directive { inner ctx Directive { inner ctx
@ -32,6 +38,8 @@ trait FutureDirectives {
* ExceptionHandler. * ExceptionHandler.
* If type `T` is already a Tuple it is directly expanded into the respective * If type `T` is already a Tuple it is directly expanded into the respective
* number of extractions. * number of extractions.
*
* @group future
*/ */
def onSuccess(magnet: OnSuccessMagnet): Directive[magnet.Out] = magnet.directive def onSuccess(magnet: OnSuccessMagnet): Directive[magnet.Out] = magnet.directive
@ -41,6 +49,8 @@ trait FutureDirectives {
* If the future succeeds the request is completed using the values marshaller * If the future succeeds the request is completed using the values marshaller
* (This directive therefore requires a marshaller for the futures type to be * (This directive therefore requires a marshaller for the futures type to be
* implicitly available.) * implicitly available.)
*
* @group future
*/ */
def completeOrRecoverWith(magnet: CompleteOrRecoverWithMagnet): Directive1[Throwable] = magnet.directive def completeOrRecoverWith(magnet: CompleteOrRecoverWithMagnet): Directive1[Throwable] = magnet.directive
} }

View file

@ -16,6 +16,10 @@ import akka.http.scaladsl.server.util.ClassMagnet
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname header Header directives
* @groupprio header 110
*/
trait HeaderDirectives { trait HeaderDirectives {
import BasicDirectives._ import BasicDirectives._
import RouteDirectives._ import RouteDirectives._
@ -24,6 +28,8 @@ trait HeaderDirectives {
* Extracts an HTTP header value using the given function. If the function result is undefined for all headers the * Extracts an HTTP header value using the given function. If the function result is undefined for all headers the
* request is rejected with an empty rejection set. If the given function throws an exception the request is rejected * request is rejected with an empty rejection set. If the given function throws an exception the request is rejected
* with a [[akka.http.scaladsl.server.MalformedHeaderRejection]]. * with a [[akka.http.scaladsl.server.MalformedHeaderRejection]].
*
* @group header
*/ */
def headerValue[T](f: HttpHeader Option[T]): Directive1[T] = { def headerValue[T](f: HttpHeader Option[T]): Directive1[T] = {
val protectedF: HttpHeader Option[Either[Rejection, T]] = header val protectedF: HttpHeader Option[Either[Rejection, T]] = header
@ -42,18 +48,24 @@ trait HeaderDirectives {
/** /**
* Extracts an HTTP header value using the given partial function. If the function is undefined for all headers the * Extracts an HTTP header value using the given partial function. If the function is undefined for all headers the
* request is rejected with an empty rejection set. * request is rejected with an empty rejection set.
*
* @group header
*/ */
def headerValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[T] = headerValue(pf.lift) def headerValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[T] = headerValue(pf.lift)
/** /**
* Extracts the value of the first HTTP request header with the given name. * Extracts the value of the first HTTP request header with the given name.
* If no header with a matching name is found the request is rejected with a [[akka.http.scaladsl.server.MissingHeaderRejection]]. * If no header with a matching name is found the request is rejected with a [[akka.http.scaladsl.server.MissingHeaderRejection]].
*
* @group header
*/ */
def headerValueByName(headerName: Symbol): Directive1[String] = headerValueByName(headerName.name) def headerValueByName(headerName: Symbol): Directive1[String] = headerValueByName(headerName.name)
/** /**
* Extracts the value of the HTTP request header with the given name. * Extracts the value of the HTTP request header with the given name.
* If no header with a matching name is found the request is rejected with a [[akka.http.scaladsl.server.MissingHeaderRejection]]. * If no header with a matching name is found the request is rejected with a [[akka.http.scaladsl.server.MissingHeaderRejection]].
*
* @group header
*/ */
def headerValueByName(headerName: String): Directive1[String] = def headerValueByName(headerName: String): Directive1[String] =
headerValue(optionalValue(headerName.toLowerCase)) | reject(MissingHeaderRejection(headerName)) headerValue(optionalValue(headerName.toLowerCase)) | reject(MissingHeaderRejection(headerName))
@ -64,6 +76,8 @@ trait HeaderDirectives {
* *
* Custom headers will only be matched by this directive if they extend [[ModeledCustomHeader]] * Custom headers will only be matched by this directive if they extend [[ModeledCustomHeader]]
* and provide a companion extending [[ModeledCustomHeaderCompanion]]. * and provide a companion extending [[ModeledCustomHeaderCompanion]].
*
* @group header
*/ */
def headerValueByType[T](magnet: HeaderMagnet[T]): Directive1[T] = def headerValueByType[T](magnet: HeaderMagnet[T]): Directive1[T] =
headerValuePF(magnet.extractPF) | reject(MissingHeaderRejection(magnet.runtimeClass.getSimpleName)) headerValuePF(magnet.extractPF) | reject(MissingHeaderRejection(magnet.runtimeClass.getSimpleName))
@ -73,6 +87,8 @@ trait HeaderDirectives {
* Extracts an optional HTTP header value using the given function. * Extracts an optional HTTP header value using the given function.
* If the given function throws an exception the request is rejected * If the given function throws an exception the request is rejected
* with a [[akka.http.scaladsl.server.MalformedHeaderRejection]]. * with a [[akka.http.scaladsl.server.MalformedHeaderRejection]].
*
* @group header
*/ */
def optionalHeaderValue[T](f: HttpHeader Option[T]): Directive1[Option[T]] = def optionalHeaderValue[T](f: HttpHeader Option[T]): Directive1[Option[T]] =
headerValue(f).map(Some(_): Option[T]).recoverPF { headerValue(f).map(Some(_): Option[T]).recoverPF {
@ -84,18 +100,24 @@ trait HeaderDirectives {
* Extracts an optional HTTP header value using the given partial function. * Extracts an optional HTTP header value using the given partial function.
* If the given function throws an exception the request is rejected * If the given function throws an exception the request is rejected
* with a [[akka.http.scaladsl.server.MalformedHeaderRejection]]. * with a [[akka.http.scaladsl.server.MalformedHeaderRejection]].
*
* @group header
*/ */
def optionalHeaderValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[Option[T]] = def optionalHeaderValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[Option[T]] =
optionalHeaderValue(pf.lift) optionalHeaderValue(pf.lift)
/** /**
* Extracts the value of the optional HTTP request header with the given name. * Extracts the value of the optional HTTP request header with the given name.
*
* @group header
*/ */
def optionalHeaderValueByName(headerName: Symbol): Directive1[Option[String]] = def optionalHeaderValueByName(headerName: Symbol): Directive1[Option[String]] =
optionalHeaderValueByName(headerName.name) optionalHeaderValueByName(headerName.name)
/** /**
* Extracts the value of the optional HTTP request header with the given name. * Extracts the value of the optional HTTP request header with the given name.
*
* @group header
*/ */
def optionalHeaderValueByName(headerName: String): Directive1[Option[String]] = { def optionalHeaderValueByName(headerName: String): Directive1[Option[String]] = {
val lowerCaseName = headerName.toLowerCase val lowerCaseName = headerName.toLowerCase
@ -109,6 +131,8 @@ trait HeaderDirectives {
* *
* Custom headers will only be matched by this directive if they extend [[ModeledCustomHeader]] * Custom headers will only be matched by this directive if they extend [[ModeledCustomHeader]]
* and provide a companion extending [[ModeledCustomHeaderCompanion]]. * and provide a companion extending [[ModeledCustomHeaderCompanion]].
*
* @group header
*/ */
def optionalHeaderValueByType[T <: HttpHeader](magnet: HeaderMagnet[T]): Directive1[Option[T]] = def optionalHeaderValueByType[T <: HttpHeader](magnet: HeaderMagnet[T]): Directive1[Option[T]] =
optionalHeaderValuePF(magnet.extractPF) optionalHeaderValuePF(magnet.extractPF)
@ -156,4 +180,4 @@ trait LowPriorityHeaderMagnetImplicits {
val runtimeClass: Class[T] = tag.runtimeClass.asInstanceOf[Class[T]] val runtimeClass: Class[T] = tag.runtimeClass.asInstanceOf[Class[T]]
val extractPF: PartialFunction[Any, T] = { case x: T x } val extractPF: PartialFunction[Any, T] = { case x: T x }
} }
} }

View file

@ -8,23 +8,33 @@ package directives
import scala.util.matching.Regex import scala.util.matching.Regex
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname host Host directives
* @groupprio host 110
*/
trait HostDirectives { trait HostDirectives {
import BasicDirectives._ import BasicDirectives._
import RouteDirectives._ import RouteDirectives._
/** /**
* Extracts the hostname part of the Host request header value. * Extracts the hostname part of the Host request header value.
*
* @group host
*/ */
def extractHost: Directive1[String] = HostDirectives._extractHost def extractHost: Directive1[String] = HostDirectives._extractHost
/** /**
* Rejects all requests with a host name different from the given ones. * Rejects all requests with a host name different from the given ones.
*
* @group host
*/ */
def host(hostNames: String*): Directive0 = host(hostNames.contains(_)) def host(hostNames: String*): Directive0 = host(hostNames.contains(_))
//#require-host //#require-host
/** /**
* Rejects all requests for whose host name the given predicate function returns false. * Rejects all requests for whose host name the given predicate function returns false.
*
* @group host
*/ */
def host(predicate: String Boolean): Directive0 = extractHost.require(predicate) def host(predicate: String Boolean): Directive0 = extractHost.require(predicate)
//# //#
@ -34,6 +44,8 @@ trait HostDirectives {
* For all matching requests the prefix string matching the regex is extracted and passed to the inner route. * For all matching requests the prefix string matching the regex is extracted and passed to the inner route.
* If the regex contains a capturing group only the string matched by this group is extracted. * If the regex contains a capturing group only the string matched by this group is extracted.
* If the regex contains more than one capturing group an IllegalArgumentException is thrown. * If the regex contains more than one capturing group an IllegalArgumentException is thrown.
*
* @group host
*/ */
def host(regex: Regex): Directive1[String] = { def host(regex: Regex): Directive1[String] = {
def forFunc(regexMatch: String Option[String]): Directive1[String] = { def forFunc(regexMatch: String Option[String]): Directive1[String] = {

View file

@ -11,6 +11,10 @@ import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.scaladsl.unmarshalling.{ Unmarshaller, FromRequestUnmarshaller } import akka.http.scaladsl.unmarshalling.{ Unmarshaller, FromRequestUnmarshaller }
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname marshalling Marshalling directives
* @groupprio marshalling 120
*/
trait MarshallingDirectives { trait MarshallingDirectives {
import BasicDirectives._ import BasicDirectives._
import FutureDirectives._ import FutureDirectives._
@ -20,6 +24,8 @@ trait MarshallingDirectives {
* Unmarshalls the requests entity to the given type passes it to its inner Route. * Unmarshalls the requests entity to the given type passes it to its inner Route.
* If there is a problem with unmarshalling the request is rejected with the [[Rejection]] * If there is a problem with unmarshalling the request is rejected with the [[Rejection]]
* produced by the unmarshaller. * produced by the unmarshaller.
*
* @group marshalling
*/ */
def entity[T](um: FromRequestUnmarshaller[T]): Directive1[T] = def entity[T](um: FromRequestUnmarshaller[T]): Directive1[T] =
extractRequestContext.flatMap[Tuple1[T]] { ctx extractRequestContext.flatMap[Tuple1[T]] { ctx
@ -36,12 +42,16 @@ trait MarshallingDirectives {
/** /**
* Returns the in-scope [[FromRequestUnmarshaller]] for the given type. * Returns the in-scope [[FromRequestUnmarshaller]] for the given type.
*
* @group marshalling
*/ */
def as[T](implicit um: FromRequestUnmarshaller[T]) = um def as[T](implicit um: FromRequestUnmarshaller[T]) = um
/** /**
* Uses the marshaller for the given type to produce a completion function that is passed to its inner function. * Uses the marshaller for the given type to produce a completion function that is passed to its inner function.
* You can use it do decouple marshaller resolution from request completion. * You can use it do decouple marshaller resolution from request completion.
*
* @group marshalling
*/ */
def completeWith[T](marshaller: ToResponseMarshaller[T])(inner: (T Unit) Unit): Route = def completeWith[T](marshaller: ToResponseMarshaller[T])(inner: (T Unit) Unit): Route =
extractRequestContext { ctx extractRequestContext { ctx
@ -55,12 +65,16 @@ trait MarshallingDirectives {
/** /**
* Returns the in-scope Marshaller for the given type. * Returns the in-scope Marshaller for the given type.
*
* @group marshalling
*/ */
def instanceOf[T](implicit m: ToResponseMarshaller[T]): ToResponseMarshaller[T] = m def instanceOf[T](implicit m: ToResponseMarshaller[T]): ToResponseMarshaller[T] = m
/** /**
* Completes the request using the given function. The input to the function is produced with the in-scope * Completes the request using the given function. The input to the function is produced with the in-scope
* entity unmarshaller and the result value of the function is marshalled with the in-scope marshaller. * entity unmarshaller and the result value of the function is marshalled with the in-scope marshaller.
*
* @group marshalling
*/ */
def handleWith[A, B](f: A B)(implicit um: FromRequestUnmarshaller[A], m: ToResponseMarshaller[B]): Route = def handleWith[A, B](f: A B)(implicit um: FromRequestUnmarshaller[A], m: ToResponseMarshaller[B]): Route =
entity(um) { a complete(f(a)) } entity(um) { a complete(f(a)) }

View file

@ -8,6 +8,10 @@ package directives
import akka.http.scaladsl.model.{ StatusCodes, HttpMethod } import akka.http.scaladsl.model.{ StatusCodes, HttpMethod }
import akka.http.scaladsl.model.HttpMethods._ import akka.http.scaladsl.model.HttpMethods._
/**
* @groupname method Method directives
* @groupprio method 130
*/
trait MethodDirectives { trait MethodDirectives {
import BasicDirectives._ import BasicDirectives._
import RouteDirectives._ import RouteDirectives._
@ -16,47 +20,65 @@ trait MethodDirectives {
/** /**
* Rejects all non-DELETE requests. * Rejects all non-DELETE requests.
*
* @group method
*/ */
def delete: Directive0 = _delete def delete: Directive0 = _delete
/** /**
* Rejects all non-GET requests. * Rejects all non-GET requests.
*
* @group method
*/ */
def get: Directive0 = _get def get: Directive0 = _get
/** /**
* Rejects all non-HEAD requests. * Rejects all non-HEAD requests.
*
* @group method
*/ */
def head: Directive0 = _head def head: Directive0 = _head
/** /**
* Rejects all non-OPTIONS requests. * Rejects all non-OPTIONS requests.
*
* @group method
*/ */
def options: Directive0 = _options def options: Directive0 = _options
/** /**
* Rejects all non-PATCH requests. * Rejects all non-PATCH requests.
*
* @group method
*/ */
def patch: Directive0 = _patch def patch: Directive0 = _patch
/** /**
* Rejects all non-POST requests. * Rejects all non-POST requests.
*
* @group method
*/ */
def post: Directive0 = _post def post: Directive0 = _post
/** /**
* Rejects all non-PUT requests. * Rejects all non-PUT requests.
*
* @group method
*/ */
def put: Directive0 = _put def put: Directive0 = _put
/** /**
* Extracts the request method. * Extracts the request method.
*
* @group method
*/ */
def extractMethod: Directive1[HttpMethod] = _extractMethod def extractMethod: Directive1[HttpMethod] = _extractMethod
//#method //#method
/** /**
* Rejects all requests whose HTTP method does not match the given one. * Rejects all requests whose HTTP method does not match the given one.
*
* @group method
*/ */
def method(httpMethod: HttpMethod): Directive0 = def method(httpMethod: HttpMethod): Directive0 =
extractMethod.flatMap[Unit] { extractMethod.flatMap[Unit] {
@ -73,6 +95,8 @@ trait MethodDirectives {
* This directive is useful for: * This directive is useful for:
* - Use in combination with JSONP (JSONP only supports GET) * - Use in combination with JSONP (JSONP only supports GET)
* - Supporting older browsers that lack support for certain HTTP methods. E.g. IE8 does not support PATCH * - Supporting older browsers that lack support for certain HTTP methods. E.g. IE8 does not support PATCH
*
* @group method
*/ */
def overrideMethodWithParameter(paramName: String): Directive0 = def overrideMethodWithParameter(paramName: String): Directive0 =
parameter(paramName?) flatMap { parameter(paramName?) flatMap {

View file

@ -8,12 +8,18 @@ package directives
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import headers._ import headers._
/**
* @groupname misc Miscellaneous directives
* @groupprio misc 140
*/
trait MiscDirectives { trait MiscDirectives {
import RouteDirectives._ import RouteDirectives._
/** /**
* Checks the given condition before running its inner route. * Checks the given condition before running its inner route.
* If the condition fails the route is rejected with a [[ValidationRejection]]. * If the condition fails the route is rejected with a [[ValidationRejection]].
*
* @group misc
*/ */
def validate(check: Boolean, errorMsg: String): Directive0 = def validate(check: Boolean, errorMsg: String): Directive0 =
Directive { inner if (check) inner(()) else reject(ValidationRejection(errorMsg)) } Directive { inner if (check) inner(()) else reject(ValidationRejection(errorMsg)) }
@ -21,17 +27,23 @@ trait MiscDirectives {
/** /**
* Extracts the client's IP from either the X-Forwarded-For, Remote-Address or X-Real-IP header * Extracts the client's IP from either the X-Forwarded-For, Remote-Address or X-Real-IP header
* (in that order of priority). * (in that order of priority).
*
* @group misc
*/ */
def extractClientIP: Directive1[RemoteAddress] = MiscDirectives._extractClientIP def extractClientIP: Directive1[RemoteAddress] = MiscDirectives._extractClientIP
/** /**
* Rejects if the request entity is non-empty. * Rejects if the request entity is non-empty.
*
* @group misc
*/ */
def requestEntityEmpty: Directive0 = MiscDirectives._requestEntityEmpty def requestEntityEmpty: Directive0 = MiscDirectives._requestEntityEmpty
/** /**
* Rejects with a [[RequestEntityExpectedRejection]] if the request entity is empty. * Rejects with a [[RequestEntityExpectedRejection]] if the request entity is empty.
* Non-empty requests are passed on unchanged to the inner route. * Non-empty requests are passed on unchanged to the inner route.
*
* @group misc
*/ */
def requestEntityPresent: Directive0 = MiscDirectives._requestEntityPresent def requestEntityPresent: Directive0 = MiscDirectives._requestEntityPresent
@ -39,6 +51,8 @@ trait MiscDirectives {
* Converts responses with an empty entity into (empty) rejections. * Converts responses with an empty entity into (empty) rejections.
* This way you can, for example, have the marshalling of a ''None'' option * This way you can, for example, have the marshalling of a ''None'' option
* be treated as if the request could not be matched. * be treated as if the request could not be matched.
*
* @group misc
*/ */
def rejectEmptyResponse: Directive0 = MiscDirectives._rejectEmptyResponse def rejectEmptyResponse: Directive0 = MiscDirectives._rejectEmptyResponse
@ -50,6 +64,8 @@ trait MiscDirectives {
* If there are several best language alternatives that the client * If there are several best language alternatives that the client
* has equal preference for (even if this preference is zero!) * has equal preference for (even if this preference is zero!)
* the order of the arguments is used as a tie breaker (First one wins). * the order of the arguments is used as a tie breaker (First one wins).
*
* @group misc
*/ */
def selectPreferredLanguage(first: Language, more: Language*): Directive1[Language] = def selectPreferredLanguage(first: Language, more: Language*): Directive1[Language] =
BasicDirectives.extractRequest.map { request BasicDirectives.extractRequest.map { request

View file

@ -11,21 +11,31 @@ import scala.util.{ Failure, Success }
import akka.http.scaladsl.common._ import akka.http.scaladsl.common._
import akka.http.impl.util._ import akka.http.impl.util._
/**
* @groupname param Parameter directives
* @groupprio param 150
*/
trait ParameterDirectives extends ToNameReceptacleEnhancements { trait ParameterDirectives extends ToNameReceptacleEnhancements {
import ParameterDirectives._ import ParameterDirectives._
/** /**
* Extracts the request's query parameters as a `Map[String, String]`. * Extracts the request's query parameters as a `Map[String, String]`.
*
* @group param
*/ */
def parameterMap: Directive1[Map[String, String]] = _parameterMap def parameterMap: Directive1[Map[String, String]] = _parameterMap
/** /**
* Extracts the request's query parameters as a `Map[String, List[String]]`. * Extracts the request's query parameters as a `Map[String, List[String]]`.
*
* @group param
*/ */
def parameterMultiMap: Directive1[Map[String, List[String]]] = _parameterMultiMap def parameterMultiMap: Directive1[Map[String, List[String]]] = _parameterMultiMap
/** /**
* Extracts the request's query parameters as a `Seq[(String, String)]`. * Extracts the request's query parameters as a `Seq[(String, String)]`.
*
* @group param
*/ */
def parameterSeq: Directive1[immutable.Seq[(String, String)]] = _parameterSeq def parameterSeq: Directive1[immutable.Seq[(String, String)]] = _parameterSeq
@ -37,6 +47,8 @@ trait ParameterDirectives extends ToNameReceptacleEnhancements {
* "too many arguments for method parameter" or "type mismatch" error. * "too many arguments for method parameter" or "type mismatch" error.
* *
* As a workaround add an `import ParameterDirectives.ParamMagnet` or use Scala 2.11.x. * As a workaround add an `import ParameterDirectives.ParamMagnet` or use Scala 2.11.x.
*
* @group param
*/ */
def parameter(pdm: ParamMagnet): pdm.Out = pdm() def parameter(pdm: ParamMagnet): pdm.Out = pdm()
@ -48,6 +60,8 @@ trait ParameterDirectives extends ToNameReceptacleEnhancements {
* "too many arguments for method parameters" or "type mismatch" error. * "too many arguments for method parameters" or "type mismatch" error.
* *
* As a workaround add an `import ParameterDirectives.ParamMagnet` or use Scala 2.11.x. * As a workaround add an `import ParameterDirectives.ParamMagnet` or use Scala 2.11.x.
*
* @group param
*/ */
def parameters(pdm: ParamMagnet): pdm.Out = pdm() def parameters(pdm: ParamMagnet): pdm.Out = pdm()
@ -170,4 +184,4 @@ object ParameterDirectives extends ParameterDirectives {
at[Directive[TA], P] { (a, t) a & pdef(t) } at[Directive[TA], P] { (a, t) a & pdef(t) }
} }
} }
} }

View file

@ -9,6 +9,10 @@ import akka.http.scaladsl.common.ToNameReceptacleEnhancements
import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.model.Uri.Path import akka.http.scaladsl.model.Uri.Path
/**
* @groupname path Path directives
* @groupprio path 170
*/
trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction with ToNameReceptacleEnhancements { trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction with ToNameReceptacleEnhancements {
import BasicDirectives._ import BasicDirectives._
import RouteDirectives._ import RouteDirectives._
@ -18,6 +22,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* Applies the given [[PathMatcher]] to the remaining unmatched path after consuming a leading slash. * Applies the given [[PathMatcher]] to the remaining unmatched path after consuming a leading slash.
* The matcher has to match the remaining path completely. * The matcher has to match the remaining path completely.
* If matched the value extracted by the [[PathMatcher]] is extracted on the directive level. * If matched the value extracted by the [[PathMatcher]] is extracted on the directive level.
*
* @group path
*/ */
def path[L](pm: PathMatcher[L]): Directive[L] = pathPrefix(pm ~ PathEnd) def path[L](pm: PathMatcher[L]): Directive[L] = pathPrefix(pm ~ PathEnd)
@ -25,6 +31,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* Applies the given [[PathMatcher]] to a prefix of the remaining unmatched path after consuming a leading slash. * Applies the given [[PathMatcher]] to a prefix of the remaining unmatched path after consuming a leading slash.
* The matcher has to match a prefix of the remaining path. * The matcher has to match a prefix of the remaining path.
* If matched the value extracted by the PathMatcher is extracted on the directive level. * If matched the value extracted by the PathMatcher is extracted on the directive level.
*
* @group path
*/ */
def pathPrefix[L](pm: PathMatcher[L]): Directive[L] = rawPathPrefix(Slash ~ pm) def pathPrefix[L](pm: PathMatcher[L]): Directive[L] = rawPathPrefix(Slash ~ pm)
@ -33,6 +41,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* [[RequestContext]] (i.e. without implicitly consuming a leading slash). * [[RequestContext]] (i.e. without implicitly consuming a leading slash).
* The matcher has to match a prefix of the remaining path. * The matcher has to match a prefix of the remaining path.
* If matched the value extracted by the PathMatcher is extracted on the directive level. * If matched the value extracted by the PathMatcher is extracted on the directive level.
*
* @group path
*/ */
def rawPathPrefix[L](pm: PathMatcher[L]): Directive[L] = { def rawPathPrefix[L](pm: PathMatcher[L]): Directive[L] = {
implicit val LIsTuple = pm.ev implicit val LIsTuple = pm.ev
@ -45,6 +55,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
/** /**
* Checks whether the unmatchedPath of the [[RequestContext]] has a prefix matched by the * Checks whether the unmatchedPath of the [[RequestContext]] has a prefix matched by the
* given PathMatcher. In analogy to the `pathPrefix` directive a leading slash is implied. * given PathMatcher. In analogy to the `pathPrefix` directive a leading slash is implied.
*
* @group path
*/ */
def pathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = rawPathPrefixTest(Slash ~ pm) def pathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = rawPathPrefixTest(Slash ~ pm)
@ -52,6 +64,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* Checks whether the unmatchedPath of the [[RequestContext]] has a prefix matched by the * Checks whether the unmatchedPath of the [[RequestContext]] has a prefix matched by the
* given PathMatcher. However, as opposed to the `pathPrefix` directive the matched path is not * given PathMatcher. However, as opposed to the `pathPrefix` directive the matched path is not
* actually "consumed". * actually "consumed".
*
* @group path
*/ */
def rawPathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = { def rawPathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = {
implicit val LIsTuple = pm.ev implicit val LIsTuple = pm.ev
@ -66,6 +80,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* If matched the value extracted by the [[PathMatcher]] is extracted and the matched parts of the path are consumed. * If matched the value extracted by the [[PathMatcher]] is extracted and the matched parts of the path are consumed.
* Note that, for efficiency reasons, the given [[PathMatcher]] must match the desired suffix in reversed-segment * Note that, for efficiency reasons, the given [[PathMatcher]] must match the desired suffix in reversed-segment
* order, i.e. `pathSuffix("baz" / "bar")` would match `/foo/bar/baz`! * order, i.e. `pathSuffix("baz" / "bar")` would match `/foo/bar/baz`!
*
* @group path
*/ */
def pathSuffix[L](pm: PathMatcher[L]): Directive[L] = { def pathSuffix[L](pm: PathMatcher[L]): Directive[L] = {
implicit val LIsTuple = pm.ev implicit val LIsTuple = pm.ev
@ -81,6 +97,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* actually "consumed". * actually "consumed".
* Note that, for efficiency reasons, the given PathMatcher must match the desired suffix in reversed-segment * Note that, for efficiency reasons, the given PathMatcher must match the desired suffix in reversed-segment
* order, i.e. `pathSuffixTest("baz" / "bar")` would match `/foo/bar/baz`! * order, i.e. `pathSuffixTest("baz" / "bar")` would match `/foo/bar/baz`!
*
* @group path
*/ */
def pathSuffixTest[L](pm: PathMatcher[L]): Directive[L] = { def pathSuffixTest[L](pm: PathMatcher[L]): Directive[L] = {
implicit val LIsTuple = pm.ev implicit val LIsTuple = pm.ev
@ -94,6 +112,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* Rejects the request if the unmatchedPath of the [[RequestContext]] is non-empty, * Rejects the request if the unmatchedPath of the [[RequestContext]] is non-empty,
* or said differently: only passes on the request to its inner route if the request path * or said differently: only passes on the request to its inner route if the request path
* has been matched completely. * has been matched completely.
*
* @group path
*/ */
def pathEnd: Directive0 = rawPathPrefix(PathEnd) def pathEnd: Directive0 = rawPathPrefix(PathEnd)
@ -124,12 +144,16 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* *
* For further information, refer to: * For further information, refer to:
* @see [[http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html]] * @see [[http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html]]
*
* @group path
*/ */
def pathEndOrSingleSlash: Directive0 = rawPathPrefix(Slash.? ~ PathEnd) def pathEndOrSingleSlash: Directive0 = rawPathPrefix(Slash.? ~ PathEnd)
/** /**
* Only passes on the request to its inner route if the request path * Only passes on the request to its inner route if the request path
* consists of exactly one remaining slash. * consists of exactly one remaining slash.
*
* @group path
*/ */
def pathSingleSlash: Directive0 = pathPrefix(PathEnd) def pathSingleSlash: Directive0 = pathPrefix(PathEnd)
@ -137,6 +161,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* If the request path doesn't end with a slash, redirect to the same uri with trailing slash in the path. * If the request path doesn't end with a slash, redirect to the same uri with trailing slash in the path.
* *
* '''Caveat''': [[path]] without trailing slash and [[pathEnd]] directives will not match inside of this directive. * '''Caveat''': [[path]] without trailing slash and [[pathEnd]] directives will not match inside of this directive.
*
* @group path
*/ */
def redirectToTrailingSlashIfMissing(redirectionType: StatusCodes.Redirection): Directive0 = def redirectToTrailingSlashIfMissing(redirectionType: StatusCodes.Redirection): Directive0 =
extractUri.flatMap { uri extractUri.flatMap { uri
@ -152,6 +178,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* If the request path ends with a slash, redirect to the same uri without trailing slash in the path. * If the request path ends with a slash, redirect to the same uri without trailing slash in the path.
* *
* '''Caveat''': [[pathSingleSlash]] directive will not match inside of this directive. * '''Caveat''': [[pathSingleSlash]] directive will not match inside of this directive.
*
* @group path
*/ */
def redirectToNoTrailingSlashIfPresent(redirectionType: StatusCodes.Redirection): Directive0 = def redirectToNoTrailingSlashIfPresent(redirectionType: StatusCodes.Redirection): Directive0 =
extractUri.flatMap { uri extractUri.flatMap { uri

View file

@ -16,6 +16,10 @@ import akka.util.ByteString
import akka.stream.SourceShape import akka.stream.SourceShape
import akka.stream.OverflowStrategy import akka.stream.OverflowStrategy
/**
* @groupname range Range directives
* @groupprio range 180
*/
trait RangeDirectives { trait RangeDirectives {
import akka.http.scaladsl.server.directives.BasicDirectives._ import akka.http.scaladsl.server.directives.BasicDirectives._
import akka.http.scaladsl.server.directives.RouteDirectives._ import akka.http.scaladsl.server.directives.RouteDirectives._
@ -33,6 +37,8 @@ trait RangeDirectives {
* on a higher level in your route structure in order to function correctly. * on a higher level in your route structure in order to function correctly.
* *
* @see [[https://tools.ietf.org/html/rfc7233]] * @see [[https://tools.ietf.org/html/rfc7233]]
*
* @group range
*/ */
def withRangeSupport: Directive0 = def withRangeSupport: Directive0 =
extractRequestContext.flatMap { ctx extractRequestContext.flatMap { ctx

View file

@ -4,28 +4,40 @@ package directives
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import scala.collection.immutable import scala.collection.immutable
/**
* @groupname response Response directives
* @groupprio response 190
*/
trait RespondWithDirectives { trait RespondWithDirectives {
import BasicDirectives._ import BasicDirectives._
/** /**
* Unconditionally adds the given response header to all HTTP responses of its inner Route. * Unconditionally adds the given response header to all HTTP responses of its inner Route.
*
* @group response
*/ */
def respondWithHeader(responseHeader: HttpHeader): Directive0 = respondWithHeaders(responseHeader) def respondWithHeader(responseHeader: HttpHeader): Directive0 = respondWithHeaders(responseHeader)
/** /**
* Adds the given response header to all HTTP responses of its inner Route, * Adds the given response header to all HTTP responses of its inner Route,
* if the response from the inner Route doesn't already contain a header with the same name. * if the response from the inner Route doesn't already contain a header with the same name.
*
* @group response
*/ */
def respondWithDefaultHeader(responseHeader: HttpHeader): Directive0 = respondWithDefaultHeaders(responseHeader) def respondWithDefaultHeader(responseHeader: HttpHeader): Directive0 = respondWithDefaultHeaders(responseHeader)
/** /**
* Unconditionally adds the given response headers to all HTTP responses of its inner Route. * Unconditionally adds the given response headers to all HTTP responses of its inner Route.
*
* @group response
*/ */
def respondWithHeaders(responseHeaders: HttpHeader*): Directive0 = def respondWithHeaders(responseHeaders: HttpHeader*): Directive0 =
respondWithHeaders(responseHeaders.toList) respondWithHeaders(responseHeaders.toList)
/** /**
* Unconditionally adds the given response headers to all HTTP responses of its inner Route. * Unconditionally adds the given response headers to all HTTP responses of its inner Route.
*
* @group response
*/ */
def respondWithHeaders(responseHeaders: immutable.Seq[HttpHeader]): Directive0 = def respondWithHeaders(responseHeaders: immutable.Seq[HttpHeader]): Directive0 =
mapResponseHeaders(responseHeaders ++ _) mapResponseHeaders(responseHeaders ++ _)
@ -33,12 +45,17 @@ trait RespondWithDirectives {
/** /**
* Adds the given response headers to all HTTP responses of its inner Route, * Adds the given response headers to all HTTP responses of its inner Route,
* if a header already exists it is not added again. * if a header already exists it is not added again.
*
* @group response
*/ */
def respondWithDefaultHeaders(responseHeaders: HttpHeader*): Directive0 = def respondWithDefaultHeaders(responseHeaders: HttpHeader*): Directive0 =
respondWithDefaultHeaders(responseHeaders.toList) respondWithDefaultHeaders(responseHeaders.toList)
/* Adds the given response headers to all HTTP responses of its inner Route, /**
* Adds the given response headers to all HTTP responses of its inner Route,
* if a header already exists it is not added again. * if a header already exists it is not added again.
*
* @group response
*/ */
def respondWithDefaultHeaders(responseHeaders: immutable.Seq[HttpHeader]): Directive0 = def respondWithDefaultHeaders(responseHeaders: immutable.Seq[HttpHeader]): Directive0 =
mapResponse(_.withDefaultHeaders(responseHeaders)) mapResponse(_.withDefaultHeaders(responseHeaders))

View file

@ -9,21 +9,31 @@ import akka.http.scaladsl.marshalling.ToResponseMarshallable
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import StatusCodes._ import StatusCodes._
/**
* @groupname route Route directives
* @groupprio route 200
*/
trait RouteDirectives { trait RouteDirectives {
/** /**
* Rejects the request with an empty set of rejections. * Rejects the request with an empty set of rejections.
*
* @group route
*/ */
def reject: StandardRoute = RouteDirectives._reject def reject: StandardRoute = RouteDirectives._reject
/** /**
* Rejects the request with the given rejections. * Rejects the request with the given rejections.
*
* @group route
*/ */
def reject(rejections: Rejection*): StandardRoute = def reject(rejections: Rejection*): StandardRoute =
StandardRoute(_.reject(rejections: _*)) StandardRoute(_.reject(rejections: _*))
/** /**
* Completes the request with redirection response of the given type to the given URI. * Completes the request with redirection response of the given type to the given URI.
*
* @group route
*/ */
def redirect(uri: Uri, redirectionType: Redirection): StandardRoute = def redirect(uri: Uri, redirectionType: Redirection): StandardRoute =
StandardRoute { StandardRoute {
@ -42,6 +52,8 @@ trait RouteDirectives {
/** /**
* Completes the request using the given arguments. * Completes the request using the given arguments.
*
* @group route
*/ */
def complete(m: ToResponseMarshallable): StandardRoute = def complete(m: ToResponseMarshallable): StandardRoute =
StandardRoute(_.complete(m)) StandardRoute(_.complete(m))
@ -49,6 +61,8 @@ trait RouteDirectives {
/** /**
* Bubbles the given error up the response chain, where it is dealt with by the closest `handleExceptions` * Bubbles the given error up the response chain, where it is dealt with by the closest `handleExceptions`
* directive and its ExceptionHandler. * directive and its ExceptionHandler.
*
* @group route
*/ */
def failWith(error: Throwable): StandardRoute = def failWith(error: Throwable): StandardRoute =
StandardRoute(_.fail(error)) StandardRoute(_.fail(error))

View file

@ -5,16 +5,24 @@
package akka.http.scaladsl.server package akka.http.scaladsl.server
package directives package directives
/**
* @groupname scheme Scheme directives
* @groupprio scheme 210
*/
trait SchemeDirectives { trait SchemeDirectives {
import BasicDirectives._ import BasicDirectives._
/** /**
* Extracts the Uri scheme from the request. * Extracts the Uri scheme from the request.
*
* @group scheme
*/ */
def extractScheme: Directive1[String] = SchemeDirectives._extractScheme def extractScheme: Directive1[String] = SchemeDirectives._extractScheme
/** /**
* Rejects all requests whose Uri scheme does not match the given one. * Rejects all requests whose Uri scheme does not match the given one.
*
* @group scheme
*/ */
def scheme(name: String): Directive0 = def scheme(name: String): Directive0 =
extractScheme.require(_ == name, SchemeRejection(name)) & cancelRejections(classOf[SchemeRejection]) extractScheme.require(_ == name, SchemeRejection(name)) & cancelRejections(classOf[SchemeRejection])

View file

@ -20,6 +20,9 @@ import scala.util.{Try, Success}
* and [[Authorization]]. Most prominently, HTTP Basic authentication as defined in RFC 2617. * and [[Authorization]]. Most prominently, HTTP Basic authentication as defined in RFC 2617.
* *
* See: <a href="https://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>. * See: <a href="https://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>.
*
* @groupname security Security directives
* @groupprio security 220
*/ */
trait SecurityDirectives { trait SecurityDirectives {
import BasicDirectives._ import BasicDirectives._
@ -31,25 +34,41 @@ trait SecurityDirectives {
/** /**
* The result of an HTTP authentication attempt is either the user object or * The result of an HTTP authentication attempt is either the user object or
* an HttpChallenge to present to the browser. * an HttpChallenge to present to the browser.
*
* @group security
*/ */
type AuthenticationResult[+T] = Either[HttpChallenge, T] type AuthenticationResult[+T] = Either[HttpChallenge, T]
//#authentication-result //#authentication-result
//#authenticator //#authenticator
/**
* @group security
*/
type Authenticator[T] = Credentials Option[T] type Authenticator[T] = Credentials Option[T]
//#authenticator //#authenticator
//#async-authenticator //#async-authenticator
/**
* @group security
*/
type AsyncAuthenticator[T] = Credentials Future[Option[T]] type AsyncAuthenticator[T] = Credentials Future[Option[T]]
//#async-authenticator //#async-authenticator
//#authenticator-pf //#authenticator-pf
/**
* @group security
*/
type AuthenticatorPF[T] = PartialFunction[Credentials, T] type AuthenticatorPF[T] = PartialFunction[Credentials, T]
//#authenticator-pf //#authenticator-pf
//#async-authenticator-pf //#async-authenticator-pf
/**
* @group security
*/
type AsyncAuthenticatorPF[T] = PartialFunction[Credentials, Future[T]] type AsyncAuthenticatorPF[T] = PartialFunction[Credentials, Future[T]]
//#async-authenticator-pf //#async-authenticator-pf
/** /**
* Extracts the potentially present [[HttpCredentials]] provided with the request's [[Authorization]] header. * Extracts the potentially present [[HttpCredentials]] provided with the request's [[Authorization]] header.
*
* @group security
*/ */
def extractCredentials: Directive1[Option[HttpCredentials]] = def extractCredentials: Directive1[Option[HttpCredentials]] =
optionalHeaderValueByType[Authorization](()).map(_.map(_.credentials)) optionalHeaderValueByType[Authorization](()).map(_.map(_.credentials))
@ -58,6 +77,8 @@ trait SecurityDirectives {
* Wraps the inner route with Http Basic authentication support using a given `Authenticator[T]`. * Wraps the inner route with Http Basic authentication support using a given `Authenticator[T]`.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateBasic[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] = def authenticateBasic[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] =
authenticateBasicAsync(realm, cred FastFuture.successful(authenticator(cred))) authenticateBasicAsync(realm, cred FastFuture.successful(authenticator(cred)))
@ -66,6 +87,8 @@ trait SecurityDirectives {
* Wraps the inner route with Http Basic authentication support. * Wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] = def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
@ -81,6 +104,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with Http Basic authentication support. * A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateBasicPF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] = def authenticateBasicPF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] =
authenticateBasic(realm, authenticator.lift) authenticateBasic(realm, authenticator.lift)
@ -89,6 +114,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with Http Basic authentication support. * A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateBasicPFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] = def authenticateBasicPFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
@ -101,6 +128,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support. * A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateOAuth2[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] = def authenticateOAuth2[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] =
authenticateOAuth2Async(realm, cred FastFuture.successful(authenticator(cred))) authenticateOAuth2Async(realm, cred FastFuture.successful(authenticator(cred)))
@ -109,6 +138,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support. * A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateOAuth2Async[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] = def authenticateOAuth2Async[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
@ -124,6 +155,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support. * A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateOAuth2PF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] = def authenticateOAuth2PF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] =
authenticateOAuth2(realm, authenticator.lift) authenticateOAuth2(realm, authenticator.lift)
@ -132,6 +165,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with OAuth2 Bearer Token authentication support. * A directive that wraps the inner route with OAuth2 Bearer Token authentication support.
* The given authenticator determines whether the credentials in the request are valid * The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route. * and, if so, which user object to supply to the inner route.
*
* @group security
*/ */
def authenticateOAuth2PFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] = def authenticateOAuth2PFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
@ -146,6 +181,7 @@ trait SecurityDirectives {
* to the inner route. If the function returns `Left(challenge)` the request is rejected with an * to the inner route. If the function returns `Left(challenge)` the request is rejected with an
* [[AuthenticationFailedRejection]] that contains this challenge to be added to the response. * [[AuthenticationFailedRejection]] that contains this challenge to be added to the response.
* *
* @group security
*/ */
def authenticateOrRejectWithChallenge[T](authenticator: Option[HttpCredentials] Future[AuthenticationResult[T]]): AuthenticationDirective[T] = def authenticateOrRejectWithChallenge[T](authenticator: Option[HttpCredentials] Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
@ -162,6 +198,8 @@ trait SecurityDirectives {
/** /**
* Lifts an authenticator function into a directive. Same as `authenticateOrRejectWithChallenge` * Lifts an authenticator function into a directive. Same as `authenticateOrRejectWithChallenge`
* but only applies the authenticator function with a certain type of credentials. * but only applies the authenticator function with a certain type of credentials.
*
* @group security
*/ */
def authenticateOrRejectWithChallenge[C <: HttpCredentials: ClassTag, T]( def authenticateOrRejectWithChallenge[C <: HttpCredentials: ClassTag, T](
authenticator: Option[C] Future[AuthenticationResult[T]]): AuthenticationDirective[T] = authenticator: Option[C] Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
@ -170,12 +208,16 @@ trait SecurityDirectives {
/** /**
* Applies the given authorization check to the request. * Applies the given authorization check to the request.
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]]. * If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/ */
def authorize(check: Boolean): Directive0 = authorize(_ check) def authorize(check: Boolean): Directive0 = authorize(_ check)
/** /**
* Applies the given authorization check to the request. * Applies the given authorization check to the request.
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]]. * If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/ */
def authorize(check: RequestContext Boolean): Directive0 = def authorize(check: RequestContext Boolean): Directive0 =
authorizeAsync(ctx => Future.successful(check(ctx))) authorizeAsync(ctx => Future.successful(check(ctx)))
@ -184,6 +226,8 @@ trait SecurityDirectives {
* Asynchronous version of [[authorize]]. * Asynchronous version of [[authorize]].
* If the [[Future]] fails or is completed with `false` * If the [[Future]] fails or is completed with `false`
* authorization fails and the route is rejected with an [[AuthorizationFailedRejection]]. * authorization fails and the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/ */
def authorizeAsync(check: Future[Boolean]): Directive0 = def authorizeAsync(check: Future[Boolean]): Directive0 =
authorizeAsync(ctx => check) authorizeAsync(ctx => check)
@ -192,6 +236,8 @@ trait SecurityDirectives {
* Asynchronous version of [[authorize]]. * Asynchronous version of [[authorize]].
* If the [[Future]] fails or is completed with `false` * If the [[Future]] fails or is completed with `false`
* authorization fails and the route is rejected with an [[AuthorizationFailedRejection]]. * authorization fails and the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/ */
def authorizeAsync(check: RequestContext Future[Boolean]): Directive0 = def authorizeAsync(check: RequestContext Future[Boolean]): Directive0 =
extractExecutionContext.flatMap { implicit ec extractExecutionContext.flatMap { implicit ec
@ -205,6 +251,8 @@ trait SecurityDirectives {
/** /**
* Creates a `Basic` [[HttpChallenge]] for the given realm. * Creates a `Basic` [[HttpChallenge]] for the given realm.
*
* @group security
*/ */
def challengeFor(realm: String) = HttpChallenge(scheme = "Basic", realm = realm, params = Map.empty) def challengeFor(realm: String) = HttpChallenge(scheme = "Basic", realm = realm, params = Map.empty)
} }

View file

@ -10,8 +10,15 @@ import akka.http.scaladsl.server.{ Directive, Directive0 }
import scala.concurrent.duration.Duration import scala.concurrent.duration.Duration
/**
* @groupname timeout Timeout directives
* @groupprio timeout 160
*/
trait TimeoutDirectives { trait TimeoutDirectives {
/**
* @group timeout
*/
def withoutRequestTimeout: Directive0 = def withoutRequestTimeout: Directive0 =
withRequestTimeout(Duration.Inf) withRequestTimeout(Duration.Inf)
@ -20,6 +27,8 @@ trait TimeoutDirectives {
* *
* Due to the inherent raciness it is not guaranteed that the update will be applied before * Due to the inherent raciness it is not guaranteed that the update will be applied before
* the previously set timeout has expired! * the previously set timeout has expired!
*
* @group timeout
*/ */
def withRequestTimeout(timeout: Duration): Directive0 = def withRequestTimeout(timeout: Duration): Directive0 =
withRequestTimeout(timeout, None) withRequestTimeout(timeout, None)
@ -31,6 +40,8 @@ trait TimeoutDirectives {
* the previously set timeout has expired! * the previously set timeout has expired!
* *
* @param handler optional custom "timeout response" function. If left None, the default timeout HttpResponse will be used. * @param handler optional custom "timeout response" function. If left None, the default timeout HttpResponse will be used.
*
* @group timeout
*/ */
def withRequestTimeout(timeout: Duration, handler: HttpRequest HttpResponse): Directive0 = def withRequestTimeout(timeout: Duration, handler: HttpRequest HttpResponse): Directive0 =
withRequestTimeout(timeout, Some(handler)) withRequestTimeout(timeout, Some(handler))
@ -42,6 +53,8 @@ trait TimeoutDirectives {
* the previously set timeout has expired! * the previously set timeout has expired!
* *
* @param handler optional custom "timeout response" function. If left None, the default timeout HttpResponse will be used. * @param handler optional custom "timeout response" function. If left None, the default timeout HttpResponse will be used.
*
* @group timeout
*/ */
def withRequestTimeout(timeout: Duration, handler: Option[HttpRequest HttpResponse]): Directive0 = def withRequestTimeout(timeout: Duration, handler: Option[HttpRequest HttpResponse]): Directive0 =
Directive { inner Directive { inner
@ -63,6 +76,8 @@ trait TimeoutDirectives {
* *
* Due to the inherent raciness it is not guaranteed that the update will be applied before * Due to the inherent raciness it is not guaranteed that the update will be applied before
* the previously set timeout has expired! * the previously set timeout has expired!
*
* @group timeout
*/ */
def withRequestTimeoutResponse(handler: HttpRequest HttpResponse): Directive0 = def withRequestTimeoutResponse(handler: HttpRequest HttpResponse): Directive0 =
Directive { inner Directive { inner
@ -76,4 +91,4 @@ trait TimeoutDirectives {
} }
object TimeoutDirectives extends TimeoutDirectives object TimeoutDirectives extends TimeoutDirectives

View file

@ -10,6 +10,10 @@ import scala.collection.immutable
import akka.http.scaladsl.model.ws.{ UpgradeToWebSocket, Message } import akka.http.scaladsl.model.ws.{ UpgradeToWebSocket, Message }
import akka.stream.scaladsl.Flow import akka.stream.scaladsl.Flow
/**
* @groupname websocket WebSocket directives
* @groupprio websocket 230
*/
trait WebSocketDirectives { trait WebSocketDirectives {
import RouteDirectives._ import RouteDirectives._
import HeaderDirectives._ import HeaderDirectives._
@ -17,6 +21,8 @@ trait WebSocketDirectives {
/** /**
* Extract the [[UpgradeToWebSocket]] header if existent. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise. * Extract the [[UpgradeToWebSocket]] header if existent. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise.
*
* @group websocket
*/ */
def extractUpgradeToWebSocket: Directive1[UpgradeToWebSocket] = def extractUpgradeToWebSocket: Directive1[UpgradeToWebSocket] =
optionalHeaderValueByType[UpgradeToWebSocket](()).flatMap { optionalHeaderValueByType[UpgradeToWebSocket](()).flatMap {
@ -27,12 +33,16 @@ trait WebSocketDirectives {
/** /**
* Extract the list of WebSocket subprotocols as offered by the client in the [[Sec-WebSocket-Protocol]] header if * Extract the list of WebSocket subprotocols as offered by the client in the [[Sec-WebSocket-Protocol]] header if
* this is a WebSocket request. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise. * this is a WebSocket request. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise.
*
* @group websocket
*/ */
def extractOfferedWsProtocols: Directive1[immutable.Seq[String]] = extractUpgradeToWebSocket.map(_.requestedProtocols) def extractOfferedWsProtocols: Directive1[immutable.Seq[String]] = extractUpgradeToWebSocket.map(_.requestedProtocols)
/** /**
* Handles WebSocket requests with the given handler and rejects other requests with an * Handles WebSocket requests with the given handler and rejects other requests with an
* [[ExpectedWebSocketRequestRejection]]. * [[ExpectedWebSocketRequestRejection]].
*
* @group websocket
*/ */
def handleWebSocketMessages(handler: Flow[Message, Message, Any]): Route = def handleWebSocketMessages(handler: Flow[Message, Message, Any]): Route =
handleWebSocketMessagesForOptionalProtocol(handler, None) handleWebSocketMessagesForOptionalProtocol(handler, None)
@ -40,6 +50,8 @@ trait WebSocketDirectives {
/** /**
* Handles WebSocket requests with the given handler if the given subprotocol is offered in the request and * Handles WebSocket requests with the given handler if the given subprotocol is offered in the request and
* rejects other requests with an [[ExpectedWebSocketRequestRejection]] or an [[UnsupportedWebSocketSubprotocolRejection]]. * rejects other requests with an [[ExpectedWebSocketRequestRejection]] or an [[UnsupportedWebSocketSubprotocolRejection]].
*
* @group websocket
*/ */
def handleWebSocketMessagesForProtocol(handler: Flow[Message, Message, Any], subprotocol: String): Route = def handleWebSocketMessagesForProtocol(handler: Flow[Message, Message, Any], subprotocol: String): Route =
handleWebSocketMessagesForOptionalProtocol(handler, Some(subprotocol)) handleWebSocketMessagesForOptionalProtocol(handler, Some(subprotocol))
@ -54,6 +66,8 @@ trait WebSocketDirectives {
* the request is rejected with an [[UnsupportedWebSocketSubprotocolRejection]] rejection. * the request is rejected with an [[UnsupportedWebSocketSubprotocolRejection]] rejection.
* *
* To support several subprotocols you may chain several `handleWebSocketMessage` Routes. * To support several subprotocols you may chain several `handleWebSocketMessage` Routes.
*
* @group websocket
*/ */
def handleWebSocketMessagesForOptionalProtocol(handler: Flow[Message, Message, Any], subprotocol: Option[String]): Route = def handleWebSocketMessagesForOptionalProtocol(handler: Flow[Message, Message, Any], subprotocol: Option[String]): Route =
extractUpgradeToWebSocket { upgrade extractUpgradeToWebSocket { upgrade

View file

@ -38,7 +38,7 @@ object Scaladoc extends AutoPlugin {
def scaladocOptions(ver: String, base: File): List[String] = { def scaladocOptions(ver: String, base: File): List[String] = {
val urlString = GitHub.url(ver) + "/€{FILE_PATH}.scala" val urlString = GitHub.url(ver) + "/€{FILE_PATH}.scala"
val opts = List("-implicits", "-doc-source-url", urlString, "-sourcepath", base.getAbsolutePath) val opts = List("-implicits", "-groups", "-doc-source-url", urlString, "-sourcepath", base.getAbsolutePath)
CliOptions.scaladocDiagramsEnabled.ifTrue("-diagrams").toList ::: opts CliOptions.scaladocDiagramsEnabled.ifTrue("-diagrams").toList ::: opts
} }