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 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 {
import PathMatcher._
/**
* Creates a PathMatcher that consumes (a prefix of) the first path segment
* (if the path begins with a segment) and extracts a given value.
*
* @group pathmatcherimpl
*/
implicit def stringExtractionPair2PathMatcher[T](tuple: (String, T)): PathMatcher1[T] =
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
* (if the path begins with a segment).
*
* @group pathmatcherimpl
*/
implicit def segmentStringToPathMatcher(segment: String): PathMatcher0 =
PathMatcher(segment :: Path.Empty, ())
/**
* @group pathmatcherimpl
*/
implicit def stringNameOptionReceptacle2PathMatcher(nr: NameOptionReceptacle[String]): PathMatcher0 =
PathMatcher(nr.name).?
@ -249,6 +260,8 @@ trait ImplicitPathMatcherConstruction {
* Extracts either the complete match (if the regex doesn't contain a capture group) or
* the capture group (if the regex contains exactly one).
* 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 {
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.
* 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.
*
* @group pathmatcherimpl
*/
implicit def valueMap2PathMatcher[T](valueMap: Map[String, T]): PathMatcher1[T] =
if (valueMap.isEmpty) PathMatchers.nothingMatcher
else valueMap.map { case (prefix, value) stringExtractionPair2PathMatcher((prefix, value)) }.reduceLeft(_ | _)
}
/**
* @groupname pathmatcher Path matchers
* @groupprio pathmatcher 171
*/
trait PathMatchers {
import PathMatcher._
/**
* Converts a path string containing slashes into a PathMatcher that interprets slashes as
* path segment separators.
*
* @group pathmatcher
*/
def separateOnSlashes(string: String): 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 ('/').
*
* @group pathmatcher
*/
object Slash extends PathMatcher0 {
def apply(path: Path) = path match {
@ -311,6 +334,8 @@ trait PathMatchers {
/**
* A PathMatcher that matches the very end of the requests URI path.
*
* @group pathmatcher
*/
object PathEnd extends PathMatcher0 {
def apply(path: Path) = path match {
@ -324,6 +349,8 @@ trait PathMatchers {
* unmatched part of the request's URI path as an (encoded!) String.
* If you need access to the remaining unencoded elements of the path
* use the `RestPath` matcher!
*
* @group pathmatcher
*/
object Rest extends PathMatcher1[String] {
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,
* unmatched part of the request's URI path.
*
* @group pathmatcher
*/
object RestPath extends PathMatcher1[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.
* The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger
* than Int.MaxValue.
*
* @group pathmatcher
*/
object IntNumber extends NumberMatcher[Int](Int.MaxValue, 10) {
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.
* The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger
* than Long.MaxValue.
*
* @group pathmatcher
*/
object LongNumber extends NumberMatcher[Long](Long.MaxValue, 10) {
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.
* The matcher will not match 0 digits or a sequence of digits that would represent an Int value larger
* than Int.MaxValue.
*
* @group pathmatcher
*/
object HexIntNumber extends NumberMatcher[Int](Int.MaxValue, 16) {
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.
* The matcher will not match 0 digits or a sequence of digits that would represent an Long value larger
* than Long.MaxValue.
*
* @group pathmatcher
*/
object HexLongNumber extends NumberMatcher[Long](Long.MaxValue, 16) {
def fromChar(c: Char) = fromHexChar(c)
}
// common implementation of Number matchers
/**
* @group pathmatcher
*/
abstract class NumberMatcher[@specialized(Int, Long) T](max: T, base: T)(implicit x: Integral[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,
* optionally signed form of a double value, i.e. without exponent.
*
* @group pathmatcher
*/
val DoubleNumber: PathMatcher1[Double] =
PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string
@ -423,6 +465,8 @@ trait PathMatchers {
/**
* A PathMatcher that matches and extracts a java.util.UUID instance.
*
* @group pathmatcher
*/
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
@ -433,12 +477,16 @@ trait PathMatchers {
/**
* A PathMatcher that always matches, doesn't consume anything and extracts nothing.
* Serves mainly as a neutral element in PathMatcher composition.
*
* @group pathmatcher
*/
val Neutral: PathMatcher0 = PathMatcher.provide(())
/**
* A PathMatcher that matches if the unmatched path starts with a path segment.
* If so the path segment is extracted as a String.
*
* @group pathmatcher
*/
object Segment extends PathMatcher1[String] {
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].
* This can also be no segments resulting in the empty list.
* 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)
@ -458,6 +508,8 @@ trait PathMatchers {
* 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 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)
@ -465,11 +517,15 @@ trait PathMatchers {
* 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.
* 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)
/**
* A PathMatcher that never matches anything.
*
* @group pathmatcher
*/
def nothingMatcher[L: Tuple]: 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._
/**
* @groupname concat Route concatenation
* @groupprio concat 300
*/
trait RouteConcatenation {
/**
* @group concat
*/
implicit def enhanceRouteWithConcatenation(route: Route): RouteConcatenation.RouteWithConcatenation =
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.util.FastFuture._
/**
* @groupname basic Basic directives
* @groupprio basic 10
*/
trait BasicDirectives {
/**
* @group basic
*/
def mapInnerRoute(f: Route Route): Directive0 =
Directive { inner f(inner(())) }
/**
* @group basic
*/
def mapRequestContext(f: RequestContext RequestContext): Directive0 =
mapInnerRoute { inner ctx inner(f(ctx)) }
/**
* @group basic
*/
def mapRequest(f: HttpRequest HttpRequest): Directive0 =
mapRequestContext(_ mapRequest f)
/**
* @group basic
*/
def mapRouteResultFuture(f: Future[RouteResult] Future[RouteResult]): Directive0 =
Directive { inner ctx f(inner(())(ctx)) }
/**
* @group basic
*/
def mapRouteResult(f: RouteResult RouteResult): Directive0 =
Directive { inner ctx inner(())(ctx).fast.map(f)(ctx.executionContext) }
/**
* @group basic
*/
def mapRouteResultWith(f: RouteResult Future[RouteResult]): Directive0 =
Directive { inner ctx inner(())(ctx).fast.flatMap(f)(ctx.executionContext) }
/**
* @group basic
*/
def mapRouteResultPF(f: PartialFunction[RouteResult, RouteResult]): Directive0 =
mapRouteResult(f.applyOrElse(_, conforms[RouteResult]))
/**
* @group basic
*/
def mapRouteResultWithPF(f: PartialFunction[RouteResult, Future[RouteResult]]): Directive0 =
mapRouteResultWith(f.applyOrElse(_, FastFuture.successful[RouteResult]))
/**
* @group basic
*/
def recoverRejections(f: immutable.Seq[Rejection] RouteResult): Directive0 =
mapRouteResultPF { case RouteResult.Rejected(rejections) f(rejections) }
/**
* @group basic
*/
def recoverRejectionsWith(f: immutable.Seq[Rejection] Future[RouteResult]): Directive0 =
mapRouteResultWithPF { case RouteResult.Rejected(rejections) f(rejections) }
/**
* @group basic
*/
def mapRejections(f: immutable.Seq[Rejection] immutable.Seq[Rejection]): Directive0 =
recoverRejections(rejections RouteResult.Rejected(f(rejections)))
/**
* @group basic
*/
def mapResponse(f: HttpResponse HttpResponse): Directive0 =
mapRouteResultPF { case RouteResult.Complete(response) RouteResult.Complete(f(response)) }
/**
* @group basic
*/
def mapResponseEntity(f: ResponseEntity ResponseEntity): Directive0 =
mapResponse(_ mapEntity f)
/**
* @group basic
*/
def mapResponseHeaders(f: immutable.Seq[HttpHeader] immutable.Seq[HttpHeader]): Directive0 =
mapResponse(_ mapHeaders f)
/**
* A Directive0 that always passes the request on to its inner route
* (i.e. does nothing with the request or the response).
*
* @group basic
*/
def pass: Directive0 = Directive.Empty
/**
* Injects the given value into a directive.
*
* @group basic
*/
def provide[T](value: T): Directive1[T] = tprovide(Tuple1(value))
/**
* Injects the given values into a directive.
*
* @group basic
*/
def tprovide[L: Tuple](values: L): Directive[L] =
Directive { _(values) }
/**
* Extracts a single value using the given function.
*
* @group basic
*/
def extract[T](f: RequestContext T): Directive1[T] =
textract(ctx Tuple1(f(ctx)))
/**
* Extracts a number of values using the given function.
*
* @group basic
*/
def textract[L: Tuple](f: RequestContext L): Directive[L] =
Directive { inner ctx inner(f(ctx))(ctx) }
@ -91,6 +147,8 @@ trait BasicDirectives {
/**
* Adds a TransformationRejection cancelling all rejections equal to the given one
* to the list of rejections potentially coming back from the inner route.
*
* @group basic
*/
def cancelRejection(rejection: Rejection): Directive0 =
cancelRejections(_ == rejection)
@ -98,6 +156,8 @@ trait BasicDirectives {
/**
* Adds a TransformationRejection cancelling all rejections of one of the given classes
* to the list of rejections potentially coming back from the inner route.
*
* @group basic
*/
def cancelRejections(classes: Class[_]*): Directive0 =
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
* to the list of rejections potentially coming back from the inner route.
*
* @group basic
*/
def cancelRejections(cancelFilter: Rejection Boolean): Directive0 =
mapRejections(_ :+ TransformationRejection(_ filterNot cancelFilter))
/**
* Transforms the unmatchedPath of the RequestContext using the given function.
*
* @group basic
*/
def mapUnmatchedPath(f: Uri.Path Uri.Path): Directive0 =
mapRequestContext(_ mapUnmatchedPath f)
/**
* Extracts the yet unmatched path from the RequestContext.
*
* @group basic
*/
def extractUnmatchedPath: Directive1[Uri.Path] = BasicDirectives._extractUnmatchedPath
/**
* Extracts the current [[HttpRequest]] instance.
*
* @group basic
*/
def extractRequest: Directive1[HttpRequest] = BasicDirectives._extractRequest
/**
* Extracts the complete request URI.
*
* @group basic
*/
def extractUri: Directive1[Uri] = BasicDirectives._extractUri
/**
* Runs its inner route with the given alternative [[scala.concurrent.ExecutionContextExecutor]].
*
* @group basic
*/
def withExecutionContext(ec: ExecutionContextExecutor): Directive0 =
mapRequestContext(_ withExecutionContext ec)
/**
* Extracts the [[scala.concurrent.ExecutionContextExecutor]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/
def extractExecutionContext: Directive1[ExecutionContextExecutor] = BasicDirectives._extractExecutionContext
/**
* Runs its inner route with the given alternative [[akka.stream.Materializer]].
*
* @group basic
*/
def withMaterializer(materializer: Materializer): Directive0 =
mapRequestContext(_ withMaterializer materializer)
/**
* Extracts the [[akka.stream.Materializer]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/
def extractMaterializer: Directive1[Materializer] = BasicDirectives._extractMaterializer
/**
* Runs its inner route with the given alternative [[akka.event.LoggingAdapter]].
*
* @group basic
*/
def withLog(log: LoggingAdapter): Directive0 =
mapRequestContext(_ withLog log)
/**
* Extracts the [[akka.event.LoggingAdapter]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/
def extractLog: Directive1[LoggingAdapter] =
BasicDirectives._extractLog
/**
* Runs its inner route with the given alternative [[RoutingSettings]].
*
* @group basic
*/
def withSettings(settings: RoutingSettings): Directive0 =
mapRequestContext(_ withRoutingSettings settings)
/**
* Runs the inner route with settings mapped by the given function.
*
* @group basic
*/
def mapSettings(f: RoutingSettings RoutingSettings): Directive0 =
mapRequestContext(ctx ctx.withRoutingSettings(f(ctx.settings)))
/**
* Extracts the [[RoutingSettings]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/
def extractSettings: Directive1[RoutingSettings] =
BasicDirectives._extractSettings
/**
* Extracts the [[akka.http.scaladsl.settings.ParserSettings]] from the [[akka.http.scaladsl.server.RequestContext]].
*
* @group basic
*/
def extractParserSettings: Directive1[ParserSettings] =
BasicDirectives._extractParserSettings
/**
* Extracts the [[akka.http.scaladsl.server.RequestContext]] itself.
*
* @group basic
*/
def extractRequestContext: Directive1[RequestContext] = BasicDirectives._extractRequestContext
}

View file

@ -12,6 +12,10 @@ import HttpMethods._
import StatusCodes._
import EntityTag._
/**
* @groupname cachecondition Cache condition directives
* @groupprio cachecondition 20
*/
trait CacheConditionDirectives {
import BasicDirectives._
import RouteDirectives._
@ -26,6 +30,8 @@ trait CacheConditionDirectives {
* 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(...)`
* 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)
@ -39,6 +45,8 @@ trait CacheConditionDirectives {
* 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(...)`
* 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))
@ -52,6 +60,8 @@ trait CacheConditionDirectives {
* 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(...)`
* 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))
@ -65,6 +75,8 @@ trait CacheConditionDirectives {
* 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(...)`
* 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 addResponseHeaders: Directive0 =

View file

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

View file

@ -9,6 +9,10 @@ import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.impl.util._
/**
* @groupname cookie Cookie directives
* @groupprio cookie 30
*/
trait CookieDirectives {
import HeaderDirectives._
import RespondWithDirectives._
@ -17,6 +21,8 @@ trait CookieDirectives {
/**
* Extracts the [[HttpCookiePair]] with the given name. If the cookie is not present the
* request is rejected with a respective [[MissingCookieRejection]].
*
* @group cookie
*/
def cookie(name: String): Directive1[HttpCookiePair] =
headerValue(findCookie(name)) | reject(MissingCookieRejection(name))
@ -24,6 +30,8 @@ trait CookieDirectives {
/**
* Extracts the [[HttpCookiePair]] with the given name as an `Option[HttpCookiePair]`.
* If the cookie is not present a value of `None` is extracted.
*
* @group cookie
*/
def optionalCookie(name: String): Directive1[Option[HttpCookiePair]] =
optionalHeaderValue(findCookie(name))
@ -35,12 +43,16 @@ trait CookieDirectives {
/**
* Adds a [[Set-Cookie]] response header with the given cookies.
*
* @group cookie
*/
def setCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
respondWithHeaders((first :: more.toList).map(`Set-Cookie`(_)))
/**
* Adds a [[Set-Cookie]] response header expiring the given cookies.
*
* @group cookie
*/
def deleteCookie(first: HttpCookie, more: HttpCookie*): Directive0 =
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.
*
* @group cookie
*/
def deleteCookie(name: String, domain: String = "", path: String = ""): Directive0 =
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.http.scaladsl.model._
/**
* @groupname debugging Debugging directives
* @groupprio debugging 40
*/
trait DebuggingDirectives {
import BasicDirectives._
/**
* Produces a log entry for every incoming request.
*
* @group debugging
*/
def logRequest(magnet: LoggingMagnet[HttpRequest Unit]): Directive0 =
extractRequestContext.flatMap { ctx
@ -23,6 +29,8 @@ trait DebuggingDirectives {
/**
* Produces a log entry for every [[RouteResult]].
*
* @group debugging
*/
def logResult(magnet: LoggingMagnet[RouteResult Unit]): Directive0 =
extractRequestContext.flatMap { ctx
@ -34,6 +42,8 @@ trait DebuggingDirectives {
/**
* Produces a log entry for every incoming request and [[RouteResult]].
*
* @group debugging
*/
def logRequestResult(magnet: LoggingMagnet[HttpRequest RouteResult Unit]): Directive0 =
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._
/**
* @groupname execution Execution directives
* @groupprio execution 60
*/
trait ExecutionDirectives {
import BasicDirectives._
/**
* Transforms exceptions thrown during evaluation of its inner route using the given
* [[akka.http.scaladsl.server.ExceptionHandler]].
*
* @group execution
*/
def handleExceptions(handler: ExceptionHandler): Directive0 =
Directive { innerRouteBuilder
@ -33,6 +39,8 @@ trait ExecutionDirectives {
/**
* Transforms rejections produced by its inner route using the given
* [[akka.http.scaladsl.server.RejectionHandler]].
*
* @group execution
*/
def handleRejections(handler: RejectionHandler): Directive0 =
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.impl.util._
/**
* @groupname fileandresource File and resource directives
* @groupprio fileandresource 70
*/
trait FileAndResourceDirectives {
import CacheConditionDirectives._
import MethodDirectives._
@ -30,6 +34,8 @@ trait FileAndResourceDirectives {
/**
* Completes GET requests with the content of the given file.
* If the file cannot be found or read the request is rejected.
*
* @group fileandresource
*/
def getFromFile(fileName: String)(implicit resolver: ContentTypeResolver): Route =
getFromFile(new File(fileName))
@ -37,6 +43,8 @@ trait FileAndResourceDirectives {
/**
* Completes GET requests with the content of the given file.
* If the file cannot be found or read the request is rejected.
*
* @group fileandresource
*/
def getFromFile(file: File)(implicit resolver: ContentTypeResolver): Route =
getFromFile(file, resolver(file.getName))
@ -44,6 +52,8 @@ trait FileAndResourceDirectives {
/**
* Completes GET requests with the content of the given file.
* If the file cannot be found or read the request is rejected.
*
* @group fileandresource
*/
def getFromFile(file: File, contentType: ContentType): Route =
get {
@ -72,6 +82,8 @@ trait FileAndResourceDirectives {
/**
* 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.
*
* @group fileandresource
*/
def getFromResource(resourceName: String)(implicit resolver: ContentTypeResolver): Route =
getFromResource(resourceName, resolver(resourceName))
@ -79,6 +91,8 @@ trait FileAndResourceDirectives {
/**
* 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.
*
* @group fileandresource
*/
def getFromResource(resourceName: String, contentType: ContentType, classLoader: ClassLoader = defaultClassLoader): Route =
if (!resourceName.endsWith("/"))
@ -104,6 +118,8 @@ trait FileAndResourceDirectives {
/**
* Completes GET requests with the content of a file underneath the given directory.
* If the file cannot be read the Route rejects the request.
*
* @group fileandresource
*/
def getFromDirectory(directoryName: String)(implicit resolver: ContentTypeResolver): Route = {
val base = withTrailingSlash(directoryName)
@ -120,6 +136,8 @@ trait FileAndResourceDirectives {
/**
* 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]`.
*
* @group fileandresource
*/
def listDirectoryContents(directories: String*)(implicit renderer: DirectoryRenderer): Route =
get {
@ -147,6 +165,8 @@ trait FileAndResourceDirectives {
/**
* Same as `getFromBrowseableDirectories` with only one directory.
*
* @group fileandresource
*/
def getFromBrowseableDirectory(directory: String)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route =
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
* served as browseable listings.
*
* @group fileandresource
*/
def getFromBrowseableDirectories(directories: String*)(implicit renderer: DirectoryRenderer, resolver: ContentTypeResolver): Route = {
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
* "resource directory".
* 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 = {
val base = if (directoryName.isEmpty) "" else withTrailingSlash(directoryName)

View file

@ -12,6 +12,10 @@ import scala.concurrent.Future
import scala.util.{ Failure, Success }
import akka.stream.scaladsl._
/**
* @groupname fileupload File upload directives
* @groupprio fileupload 80
*/
trait FileUploadDirectives {
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
* 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.
*
* @group fileupload
*/
def uploadedFile(fieldName: String): Directive1[(FileInfo, File)] =
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,
* if there are multiple file parts with the same name, the first one will be used and the subsequent
* ones ignored.
*
* @group fileupload
*/
def fileUpload(fieldName: String): Directive1[(FileInfo, Source[ByteString, Any])] =
entity(as[Multipart.FormData]).flatMap { formData

View file

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

View file

@ -13,11 +13,17 @@ import akka.http.scaladsl.util.FastFuture._
// format: OFF
/**
* @groupname future Future directives
* @groupprio future 100
*/
trait FutureDirectives {
/**
* "Unwraps" a `Future[T]` and runs the inner route after future
* completion with the future's value as an extraction of type `Try[T]`.
*
* @group future
*/
def onComplete[T](future: Future[T]): Directive1[Try[T]] =
Directive { inner ctx
@ -32,6 +38,8 @@ trait FutureDirectives {
* ExceptionHandler.
* If type `T` is already a Tuple it is directly expanded into the respective
* number of extractions.
*
* @group future
*/
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
* (This directive therefore requires a marshaller for the futures type to be
* implicitly available.)
*
* @group future
*/
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.impl.util._
/**
* @groupname header Header directives
* @groupprio header 110
*/
trait HeaderDirectives {
import BasicDirectives._
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
* 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]].
*
* @group header
*/
def headerValue[T](f: HttpHeader Option[T]): Directive1[T] = {
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
* request is rejected with an empty rejection set.
*
* @group header
*/
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.
* 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)
/**
* 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]].
*
* @group header
*/
def headerValueByName(headerName: String): Directive1[String] =
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]]
* and provide a companion extending [[ModeledCustomHeaderCompanion]].
*
* @group header
*/
def headerValueByType[T](magnet: HeaderMagnet[T]): Directive1[T] =
headerValuePF(magnet.extractPF) | reject(MissingHeaderRejection(magnet.runtimeClass.getSimpleName))
@ -73,6 +87,8 @@ trait HeaderDirectives {
* Extracts an optional HTTP header value using the given function.
* If the given function throws an exception the request is rejected
* with a [[akka.http.scaladsl.server.MalformedHeaderRejection]].
*
* @group header
*/
def optionalHeaderValue[T](f: HttpHeader Option[T]): Directive1[Option[T]] =
headerValue(f).map(Some(_): Option[T]).recoverPF {
@ -84,18 +100,24 @@ trait HeaderDirectives {
* Extracts an optional HTTP header value using the given partial function.
* If the given function throws an exception the request is rejected
* with a [[akka.http.scaladsl.server.MalformedHeaderRejection]].
*
* @group header
*/
def optionalHeaderValuePF[T](pf: PartialFunction[HttpHeader, T]): Directive1[Option[T]] =
optionalHeaderValue(pf.lift)
/**
* Extracts the value of the optional HTTP request header with the given name.
*
* @group header
*/
def optionalHeaderValueByName(headerName: Symbol): Directive1[Option[String]] =
optionalHeaderValueByName(headerName.name)
/**
* Extracts the value of the optional HTTP request header with the given name.
*
* @group header
*/
def optionalHeaderValueByName(headerName: String): Directive1[Option[String]] = {
val lowerCaseName = headerName.toLowerCase
@ -109,6 +131,8 @@ trait HeaderDirectives {
*
* Custom headers will only be matched by this directive if they extend [[ModeledCustomHeader]]
* and provide a companion extending [[ModeledCustomHeaderCompanion]].
*
* @group header
*/
def optionalHeaderValueByType[T <: HttpHeader](magnet: HeaderMagnet[T]): Directive1[Option[T]] =
optionalHeaderValuePF(magnet.extractPF)
@ -156,4 +180,4 @@ trait LowPriorityHeaderMagnetImplicits {
val runtimeClass: Class[T] = tag.runtimeClass.asInstanceOf[Class[T]]
val extractPF: PartialFunction[Any, T] = { case x: T x }
}
}
}

View file

@ -8,23 +8,33 @@ package directives
import scala.util.matching.Regex
import akka.http.impl.util._
/**
* @groupname host Host directives
* @groupprio host 110
*/
trait HostDirectives {
import BasicDirectives._
import RouteDirectives._
/**
* Extracts the hostname part of the Host request header value.
*
* @group host
*/
def extractHost: Directive1[String] = HostDirectives._extractHost
/**
* Rejects all requests with a host name different from the given ones.
*
* @group host
*/
def host(hostNames: String*): Directive0 = host(hostNames.contains(_))
//#require-host
/**
* Rejects all requests for whose host name the given predicate function returns false.
*
* @group host
*/
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.
* 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.
*
* @group host
*/
def host(regex: Regex): 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.impl.util._
/**
* @groupname marshalling Marshalling directives
* @groupprio marshalling 120
*/
trait MarshallingDirectives {
import BasicDirectives._
import FutureDirectives._
@ -20,6 +24,8 @@ trait MarshallingDirectives {
* 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]]
* produced by the unmarshaller.
*
* @group marshalling
*/
def entity[T](um: FromRequestUnmarshaller[T]): Directive1[T] =
extractRequestContext.flatMap[Tuple1[T]] { ctx
@ -36,12 +42,16 @@ trait MarshallingDirectives {
/**
* Returns the in-scope [[FromRequestUnmarshaller]] for the given type.
*
* @group marshalling
*/
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.
* You can use it do decouple marshaller resolution from request completion.
*
* @group marshalling
*/
def completeWith[T](marshaller: ToResponseMarshaller[T])(inner: (T Unit) Unit): Route =
extractRequestContext { ctx
@ -55,12 +65,16 @@ trait MarshallingDirectives {
/**
* Returns the in-scope Marshaller for the given type.
*
* @group marshalling
*/
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
* 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 =
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.HttpMethods._
/**
* @groupname method Method directives
* @groupprio method 130
*/
trait MethodDirectives {
import BasicDirectives._
import RouteDirectives._
@ -16,47 +20,65 @@ trait MethodDirectives {
/**
* Rejects all non-DELETE requests.
*
* @group method
*/
def delete: Directive0 = _delete
/**
* Rejects all non-GET requests.
*
* @group method
*/
def get: Directive0 = _get
/**
* Rejects all non-HEAD requests.
*
* @group method
*/
def head: Directive0 = _head
/**
* Rejects all non-OPTIONS requests.
*
* @group method
*/
def options: Directive0 = _options
/**
* Rejects all non-PATCH requests.
*
* @group method
*/
def patch: Directive0 = _patch
/**
* Rejects all non-POST requests.
*
* @group method
*/
def post: Directive0 = _post
/**
* Rejects all non-PUT requests.
*
* @group method
*/
def put: Directive0 = _put
/**
* Extracts the request method.
*
* @group method
*/
def extractMethod: Directive1[HttpMethod] = _extractMethod
//#method
/**
* Rejects all requests whose HTTP method does not match the given one.
*
* @group method
*/
def method(httpMethod: HttpMethod): Directive0 =
extractMethod.flatMap[Unit] {
@ -73,6 +95,8 @@ trait MethodDirectives {
* This directive is useful for:
* - 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
*
* @group method
*/
def overrideMethodWithParameter(paramName: String): Directive0 =
parameter(paramName?) flatMap {

View file

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

View file

@ -11,21 +11,31 @@ import scala.util.{ Failure, Success }
import akka.http.scaladsl.common._
import akka.http.impl.util._
/**
* @groupname param Parameter directives
* @groupprio param 150
*/
trait ParameterDirectives extends ToNameReceptacleEnhancements {
import ParameterDirectives._
/**
* Extracts the request's query parameters as a `Map[String, String]`.
*
* @group param
*/
def parameterMap: Directive1[Map[String, String]] = _parameterMap
/**
* Extracts the request's query parameters as a `Map[String, List[String]]`.
*
* @group param
*/
def parameterMultiMap: Directive1[Map[String, List[String]]] = _parameterMultiMap
/**
* Extracts the request's query parameters as a `Seq[(String, String)]`.
*
* @group param
*/
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.
*
* As a workaround add an `import ParameterDirectives.ParamMagnet` or use Scala 2.11.x.
*
* @group param
*/
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.
*
* As a workaround add an `import ParameterDirectives.ParamMagnet` or use Scala 2.11.x.
*
* @group param
*/
def parameters(pdm: ParamMagnet): pdm.Out = pdm()
@ -170,4 +184,4 @@ object ParameterDirectives extends ParameterDirectives {
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.Uri.Path
/**
* @groupname path Path directives
* @groupprio path 170
*/
trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction with ToNameReceptacleEnhancements {
import BasicDirectives._
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.
* The matcher has to match the remaining path completely.
* 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)
@ -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.
* 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.
*
* @group path
*/
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).
* 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.
*
* @group path
*/
def rawPathPrefix[L](pm: PathMatcher[L]): Directive[L] = {
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
* 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)
@ -52,6 +64,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* 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
* actually "consumed".
*
* @group path
*/
def rawPathPrefixTest[L](pm: PathMatcher[L]): Directive[L] = {
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.
* 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`!
*
* @group path
*/
def pathSuffix[L](pm: PathMatcher[L]): Directive[L] = {
implicit val LIsTuple = pm.ev
@ -81,6 +97,8 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
* actually "consumed".
* 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`!
*
* @group path
*/
def pathSuffixTest[L](pm: PathMatcher[L]): Directive[L] = {
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,
* or said differently: only passes on the request to its inner route if the request path
* has been matched completely.
*
* @group path
*/
def pathEnd: Directive0 = rawPathPrefix(PathEnd)
@ -124,12 +144,16 @@ trait PathDirectives extends PathMatchers with ImplicitPathMatcherConstruction w
*
* For further information, refer to:
* @see [[http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html]]
*
* @group path
*/
def pathEndOrSingleSlash: Directive0 = rawPathPrefix(Slash.? ~ PathEnd)
/**
* Only passes on the request to its inner route if the request path
* consists of exactly one remaining slash.
*
* @group path
*/
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.
*
* '''Caveat''': [[path]] without trailing slash and [[pathEnd]] directives will not match inside of this directive.
*
* @group path
*/
def redirectToTrailingSlashIfMissing(redirectionType: StatusCodes.Redirection): Directive0 =
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.
*
* '''Caveat''': [[pathSingleSlash]] directive will not match inside of this directive.
*
* @group path
*/
def redirectToNoTrailingSlashIfPresent(redirectionType: StatusCodes.Redirection): Directive0 =
extractUri.flatMap { uri

View file

@ -16,6 +16,10 @@ import akka.util.ByteString
import akka.stream.SourceShape
import akka.stream.OverflowStrategy
/**
* @groupname range Range directives
* @groupprio range 180
*/
trait RangeDirectives {
import akka.http.scaladsl.server.directives.BasicDirectives._
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.
*
* @see [[https://tools.ietf.org/html/rfc7233]]
*
* @group range
*/
def withRangeSupport: Directive0 =
extractRequestContext.flatMap { ctx

View file

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

View file

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

View file

@ -5,16 +5,24 @@
package akka.http.scaladsl.server
package directives
/**
* @groupname scheme Scheme directives
* @groupprio scheme 210
*/
trait SchemeDirectives {
import BasicDirectives._
/**
* Extracts the Uri scheme from the request.
*
* @group scheme
*/
def extractScheme: Directive1[String] = SchemeDirectives._extractScheme
/**
* Rejects all requests whose Uri scheme does not match the given one.
*
* @group scheme
*/
def scheme(name: String): Directive0 =
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.
*
* See: <a href="https://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>.
*
* @groupname security Security directives
* @groupprio security 220
*/
trait SecurityDirectives {
import BasicDirectives._
@ -31,25 +34,41 @@ trait SecurityDirectives {
/**
* The result of an HTTP authentication attempt is either the user object or
* an HttpChallenge to present to the browser.
*
* @group security
*/
type AuthenticationResult[+T] = Either[HttpChallenge, T]
//#authentication-result
//#authenticator
/**
* @group security
*/
type Authenticator[T] = Credentials Option[T]
//#authenticator
//#async-authenticator
/**
* @group security
*/
type AsyncAuthenticator[T] = Credentials Future[Option[T]]
//#async-authenticator
//#authenticator-pf
/**
* @group security
*/
type AuthenticatorPF[T] = PartialFunction[Credentials, T]
//#authenticator-pf
//#async-authenticator-pf
/**
* @group security
*/
type AsyncAuthenticatorPF[T] = PartialFunction[Credentials, Future[T]]
//#async-authenticator-pf
/**
* Extracts the potentially present [[HttpCredentials]] provided with the request's [[Authorization]] header.
*
* @group security
*/
def extractCredentials: Directive1[Option[HttpCredentials]] =
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]`.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateBasic[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] =
authenticateBasicAsync(realm, cred FastFuture.successful(authenticator(cred)))
@ -66,6 +87,8 @@ trait SecurityDirectives {
* Wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateBasicAsync[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
@ -81,6 +104,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateBasicPF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] =
authenticateBasic(realm, authenticator.lift)
@ -89,6 +114,8 @@ trait SecurityDirectives {
* A directive that wraps the inner route with Http Basic authentication support.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateBasicPFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
@ -101,6 +128,8 @@ trait SecurityDirectives {
* 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
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateOAuth2[T](realm: String, authenticator: Authenticator[T]): AuthenticationDirective[T] =
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.
* The given authenticator determines whether the credentials in the request are valid
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateOAuth2Async[T](realm: String, authenticator: AsyncAuthenticator[T]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
@ -124,6 +155,8 @@ trait SecurityDirectives {
* 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
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateOAuth2PF[T](realm: String, authenticator: AuthenticatorPF[T]): AuthenticationDirective[T] =
authenticateOAuth2(realm, authenticator.lift)
@ -132,6 +165,8 @@ trait SecurityDirectives {
* 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
* and, if so, which user object to supply to the inner route.
*
* @group security
*/
def authenticateOAuth2PFAsync[T](realm: String, authenticator: AsyncAuthenticatorPF[T]): AuthenticationDirective[T] =
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
* [[AuthenticationFailedRejection]] that contains this challenge to be added to the response.
*
* @group security
*/
def authenticateOrRejectWithChallenge[T](authenticator: Option[HttpCredentials] Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
extractExecutionContext.flatMap { implicit ec
@ -162,6 +198,8 @@ trait SecurityDirectives {
/**
* Lifts an authenticator function into a directive. Same as `authenticateOrRejectWithChallenge`
* but only applies the authenticator function with a certain type of credentials.
*
* @group security
*/
def authenticateOrRejectWithChallenge[C <: HttpCredentials: ClassTag, T](
authenticator: Option[C] Future[AuthenticationResult[T]]): AuthenticationDirective[T] =
@ -170,12 +208,16 @@ trait SecurityDirectives {
/**
* Applies the given authorization check to the request.
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/
def authorize(check: Boolean): Directive0 = authorize(_ check)
/**
* Applies the given authorization check to the request.
* If the check fails the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/
def authorize(check: RequestContext Boolean): Directive0 =
authorizeAsync(ctx => Future.successful(check(ctx)))
@ -184,6 +226,8 @@ trait SecurityDirectives {
* Asynchronous version of [[authorize]].
* If the [[Future]] fails or is completed with `false`
* authorization fails and the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/
def authorizeAsync(check: Future[Boolean]): Directive0 =
authorizeAsync(ctx => check)
@ -192,6 +236,8 @@ trait SecurityDirectives {
* Asynchronous version of [[authorize]].
* If the [[Future]] fails or is completed with `false`
* authorization fails and the route is rejected with an [[AuthorizationFailedRejection]].
*
* @group security
*/
def authorizeAsync(check: RequestContext Future[Boolean]): Directive0 =
extractExecutionContext.flatMap { implicit ec
@ -205,6 +251,8 @@ trait SecurityDirectives {
/**
* Creates a `Basic` [[HttpChallenge]] for the given realm.
*
* @group security
*/
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
/**
* @groupname timeout Timeout directives
* @groupprio timeout 160
*/
trait TimeoutDirectives {
/**
* @group timeout
*/
def withoutRequestTimeout: Directive0 =
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
* the previously set timeout has expired!
*
* @group timeout
*/
def withRequestTimeout(timeout: Duration): Directive0 =
withRequestTimeout(timeout, None)
@ -31,6 +40,8 @@ trait TimeoutDirectives {
* the previously set timeout has expired!
*
* @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 =
withRequestTimeout(timeout, Some(handler))
@ -42,6 +53,8 @@ trait TimeoutDirectives {
* the previously set timeout has expired!
*
* @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 =
Directive { inner
@ -63,6 +76,8 @@ trait TimeoutDirectives {
*
* Due to the inherent raciness it is not guaranteed that the update will be applied before
* the previously set timeout has expired!
*
* @group timeout
*/
def withRequestTimeoutResponse(handler: HttpRequest HttpResponse): Directive0 =
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.stream.scaladsl.Flow
/**
* @groupname websocket WebSocket directives
* @groupprio websocket 230
*/
trait WebSocketDirectives {
import RouteDirectives._
import HeaderDirectives._
@ -17,6 +21,8 @@ trait WebSocketDirectives {
/**
* Extract the [[UpgradeToWebSocket]] header if existent. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise.
*
* @group websocket
*/
def extractUpgradeToWebSocket: Directive1[UpgradeToWebSocket] =
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
* this is a WebSocket request. Rejects with an [[ExpectedWebSocketRequestRejection]], otherwise.
*
* @group websocket
*/
def extractOfferedWsProtocols: Directive1[immutable.Seq[String]] = extractUpgradeToWebSocket.map(_.requestedProtocols)
/**
* Handles WebSocket requests with the given handler and rejects other requests with an
* [[ExpectedWebSocketRequestRejection]].
*
* @group websocket
*/
def handleWebSocketMessages(handler: Flow[Message, Message, Any]): Route =
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
* rejects other requests with an [[ExpectedWebSocketRequestRejection]] or an [[UnsupportedWebSocketSubprotocolRejection]].
*
* @group websocket
*/
def handleWebSocketMessagesForProtocol(handler: Flow[Message, Message, Any], subprotocol: String): Route =
handleWebSocketMessagesForOptionalProtocol(handler, Some(subprotocol))
@ -54,6 +66,8 @@ trait WebSocketDirectives {
* the request is rejected with an [[UnsupportedWebSocketSubprotocolRejection]] rejection.
*
* To support several subprotocols you may chain several `handleWebSocketMessage` Routes.
*
* @group websocket
*/
def handleWebSocketMessagesForOptionalProtocol(handler: Flow[Message, Message, Any], subprotocol: Option[String]): Route =
extractUpgradeToWebSocket { upgrade

View file

@ -38,7 +38,7 @@ object Scaladoc extends AutoPlugin {
def scaladocOptions(ver: String, base: File): List[String] = {
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
}