Skip to content

Commit

Permalink
Adds missing parameter with required value directive for Java API (#2025
Browse files Browse the repository at this point in the history
) (#2026)
  • Loading branch information
Korneliusz Rabczak authored and raboof committed Jun 5, 2018
1 parent 0e5125d commit fd94b5d
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;

import org.junit.Test;
Expand All @@ -16,8 +15,6 @@
import akka.http.javadsl.testkit.JUnitRouteTest;
import akka.http.javadsl.testkit.TestRoute;

import static akka.http.javadsl.server.Directives.*;

public class ParameterDirectivesTest extends JUnitRouteTest {

@Test
Expand Down Expand Up @@ -380,4 +377,25 @@ public void testOptionalIntParameterExtraction() {
.assertEntity("Optional[23]");
}

@Test
public void testRequiredParameterExtraction() {
TestRoute route = testRoute(parameterRequiredValue(StringUnmarshallers.INTEGER, 1, "requiredIntParam", () -> complete("OK")));

route
.run(HttpRequest.create().withUri("/abc?someParameter=1"))
.assertStatusCode(404)
.assertEntity("Request is missing required query parameter 'requiredIntParam'");

route
.run(HttpRequest.create().withUri("/abc?requiredIntParam=12"))
.assertStatusCode(404)
.assertEntity("Request is missing required value '1' for query parameter 'requiredIntParam'");

route
.run(HttpRequest.create().withUri("/abc?requiredIntParam=1"))
.assertStatusCode(200)
.assertEntity("OK");
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,15 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi
}

"The 'parameter' requirement directive should" - {
"block requests that do not contain the required parameter" in {
"reject the request with a MissingQueryParamRejection if request do not contain the required parameter" in {
Get("/person?age=19") ~> {
parameter('nose ! "large") { completeOk }
} ~> check { handled shouldEqual false }
} ~> check { rejection shouldEqual MissingQueryParamRejection("nose") }
}
"block requests that contain the required parameter but with an unmatching value" in {
"reject the request with a InvalidRequiredValueForQueryParamRejection if the required parameter has an unmatching value" in {
Get("/person?age=19&nose=small") ~> {
parameter('nose ! "large") { completeOk }
} ~> check { handled shouldEqual false }
} ~> check { rejection shouldEqual InvalidRequiredValueForQueryParamRejection("nose", "large", "small") }
}
"let requests pass that contain the required parameter with its required value" in {
Get("/person?nose=large&eyes=blue") ~> {
Expand Down
40 changes: 40 additions & 0 deletions akka-http/src/main/scala/akka/http/javadsl/server/Rejections.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java.util.Optional
import java.util.function.{ Function JFunction }
import java.lang.{ Iterable JIterable }

import akka.annotation.DoNotInherit
import akka.http.scaladsl
import akka.japi.Util
import akka.pattern.CircuitBreakerOpenException
Expand All @@ -37,6 +38,7 @@ trait CustomRejection extends akka.http.scaladsl.server.Rejection
* Rejection created by method filters.
* Signals that the request was rejected because the HTTP method is unsupported.
*/
@DoNotInherit
trait MethodRejection extends Rejection {
def supported: HttpMethod
}
Expand All @@ -45,6 +47,7 @@ trait MethodRejection extends Rejection {
* Rejection created by scheme filters.
* Signals that the request was rejected because the Uri scheme is unsupported.
*/
@DoNotInherit
trait SchemeRejection extends Rejection {
def supported: String
}
Expand All @@ -53,14 +56,27 @@ trait SchemeRejection extends Rejection {
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter was not found.
*/
@DoNotInherit
trait MissingQueryParamRejection extends Rejection {
def parameterName: String
}

/**
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter value was not equal to required one.
*/
@DoNotInherit
trait InvalidRequiredValueForQueryParamRejection extends Rejection {
def parameterName: String
def expectedValue: String
def actualValue: String
}

/**
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter could not be interpreted.
*/
@DoNotInherit
trait MalformedQueryParamRejection extends Rejection {
def parameterName: String
def errorMsg: String
Expand All @@ -71,6 +87,7 @@ trait MalformedQueryParamRejection extends Rejection {
* Rejection created by form field filters.
* Signals that the request was rejected because a form field was not found.
*/
@DoNotInherit
trait MissingFormFieldRejection extends Rejection {
def fieldName: String
}
Expand All @@ -79,6 +96,7 @@ trait MissingFormFieldRejection extends Rejection {
* Rejection created by form field filters.
* Signals that the request was rejected because a form field could not be interpreted.
*/
@DoNotInherit
trait MalformedFormFieldRejection extends Rejection {
def fieldName: String
def errorMsg: String
Expand All @@ -89,6 +107,7 @@ trait MalformedFormFieldRejection extends Rejection {
* Rejection created by header directives.
* Signals that the request was rejected because a required header could not be found.
*/
@DoNotInherit
trait MissingHeaderRejection extends Rejection {
def headerName: String
}
Expand All @@ -97,6 +116,7 @@ trait MissingHeaderRejection extends Rejection {
* Rejection created by header directives.
* Signals that the request was rejected because a header value is malformed.
*/
@DoNotInherit
trait MalformedHeaderRejection extends Rejection {
def headerName: String
def errorMsg: String
Expand All @@ -107,6 +127,7 @@ trait MalformedHeaderRejection extends Rejection {
* Rejection created by [[akka.http.scaladsl.server.directives.HeaderDirectives.checkSameOrigin]].
* Signals that the request was rejected because `Origin` header value is invalid.
*/
@DoNotInherit
trait InvalidOriginRejection extends Rejection {
def getAllowedOrigins: java.util.List[akka.http.javadsl.model.headers.HttpOrigin]
}
Expand All @@ -115,6 +136,7 @@ trait InvalidOriginRejection extends Rejection {
* Rejection created by unmarshallers.
* Signals that the request was rejected because the requests content-type is unsupported.
*/
@DoNotInherit
trait UnsupportedRequestContentTypeRejection extends Rejection {
def getSupported: java.util.Set[akka.http.javadsl.model.ContentTypeRange]
}
Expand All @@ -123,6 +145,7 @@ trait UnsupportedRequestContentTypeRejection extends Rejection {
* Rejection created by decoding filters.
* Signals that the request was rejected because the requests content encoding is unsupported.
*/
@DoNotInherit
trait UnsupportedRequestEncodingRejection extends Rejection {
def supported: HttpEncoding
}
Expand All @@ -132,6 +155,7 @@ trait UnsupportedRequestEncodingRejection extends Rejection {
* Signals that the request was rejected because the requests contains only unsatisfiable ByteRanges.
* The actualEntityLength gives the client a hint to create satisfiable ByteRanges.
*/
@DoNotInherit
trait UnsatisfiableRangeRejection extends Rejection {
def getUnsatisfiableRanges: JIterable[ByteRange]
def actualEntityLength: Long
Expand All @@ -142,6 +166,7 @@ trait UnsatisfiableRangeRejection extends Rejection {
* Signals that the request contains too many ranges. An irregular high number of ranges
* indicates a broken client or a denial of service attack.
*/
@DoNotInherit
trait TooManyRangesRejection extends Rejection {
def maxRanges: Int
}
Expand All @@ -153,6 +178,7 @@ trait TooManyRangesRejection extends Rejection {
* Note that semantic issues with the request content (e.g. because some parameter was out of range)
* will usually trigger a `ValidationRejection` instead.
*/
@DoNotInherit
trait MalformedRequestContentRejection extends Rejection {
def message: String
def getCause: Throwable
Expand All @@ -162,6 +188,7 @@ trait MalformedRequestContentRejection extends Rejection {
* Rejection created by unmarshallers.
* Signals that the request was rejected because an message body entity was expected but not supplied.
*/
@DoNotInherit
abstract class RequestEntityExpectedRejection extends Rejection
object RequestEntityExpectedRejection {
def get: RequestEntityExpectedRejection = scaladsl.server.RequestEntityExpectedRejection
Expand All @@ -172,6 +199,7 @@ object RequestEntityExpectedRejection {
* Signals that the request was rejected because the service is not capable of producing a response entity whose
* content type is accepted by the client
*/
@DoNotInherit
trait UnacceptedResponseContentTypeRejection extends Rejection {
def supported: immutable.Set[ContentNegotiator.Alternative]
}
Expand All @@ -181,6 +209,7 @@ trait UnacceptedResponseContentTypeRejection extends Rejection {
* Signals that the request was rejected because the service is not capable of producing a response entity whose
* content encoding is accepted by the client
*/
@DoNotInherit
trait UnacceptedResponseEncodingRejection extends Rejection {
def getSupported: java.util.Set[HttpEncoding]
}
Expand All @@ -194,6 +223,7 @@ object UnacceptedResponseEncodingRejection {
* Signals that the request was rejected because the user could not be authenticated. The reason for the rejection is
* specified in the cause.
*/
@DoNotInherit
trait AuthenticationFailedRejection extends Rejection {
def cause: AuthenticationFailedRejection.Cause
def challenge: HttpChallenge
Expand Down Expand Up @@ -222,6 +252,7 @@ object AuthenticationFailedRejection {
* Rejection created by the 'authorize' directive.
* Signals that the request was rejected because the user is not authorized.
*/
@DoNotInherit
trait AuthorizationFailedRejection extends Rejection
object AuthorizationFailedRejection {
def get = scaladsl.server.AuthorizationFailedRejection
Expand All @@ -231,13 +262,15 @@ object AuthorizationFailedRejection {
* Rejection created by the `cookie` directive.
* Signals that the request was rejected because a cookie was not found.
*/
@DoNotInherit
trait MissingCookieRejection extends Rejection {
def cookieName: String
}

/**
* Rejection created when a websocket request was expected but none was found.
*/
@DoNotInherit
trait ExpectedWebSocketRequestRejection extends Rejection
object ExpectedWebSocketRequestRejection {
def get: ExpectedWebSocketRequestRejection = scaladsl.server.ExpectedWebSocketRequestRejection
Expand All @@ -247,6 +280,7 @@ object ExpectedWebSocketRequestRejection {
* Rejection created when a websocket request was not handled because none of the given subprotocols
* was supported.
*/
@DoNotInherit
trait UnsupportedWebSocketSubprotocolRejection extends Rejection {
def supportedProtocol: String
}
Expand All @@ -256,6 +290,7 @@ trait UnsupportedWebSocketSubprotocolRejection extends Rejection {
* thrown by domain model constructors (e.g. via `require`).
* It signals that an expected value was semantically invalid.
*/
@DoNotInherit
trait ValidationRejection extends Rejection {
def message: String
def getCause: Optional[Throwable]
Expand All @@ -265,6 +300,7 @@ trait ValidationRejection extends Rejection {
* Rejection created by the `onCompleteWithBreaker` directive.
* Signals that the request was rejected because the supplied circuit breaker is open and requests are failing fast.
*/
@DoNotInherit
trait CircuitBreakerOpenRejection extends Rejection {
def cause: CircuitBreakerOpenException
}
Expand All @@ -287,6 +323,7 @@ trait CircuitBreakerOpenRejection extends Rejection {
* MethodRejection added by the `get` directive is canceled by the `put` directive (since the HTTP method
* did indeed match eventually).
*/
@DoNotInherit
trait TransformationRejection extends Rejection {
def getTransform: JFunction[JIterable[Rejection], JIterable[Rejection]]
}
Expand Down Expand Up @@ -315,6 +352,9 @@ object Rejections {
def missingQueryParam(parameterName: String): MissingQueryParamRejection =
s.MissingQueryParamRejection(parameterName)

def invalidRequiredValueForQueryParam(parameterName: String, requiredValue: String, actualValue: String): InvalidRequiredValueForQueryParamRejection =
s.InvalidRequiredValueForQueryParamRejection(parameterName, requiredValue, actualValue)

def malformedQueryParam(parameterName: String, errorMsg: String): MalformedQueryParamRejection =
s.MalformedQueryParamRejection(parameterName, errorMsg)
def malformedQueryParam(parameterName: String, errorMsg: String, cause: Optional[Throwable]): MalformedQueryParamRejection =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@

package akka.http.javadsl.server.directives

import java.util.{ Map JMap, List JList }
import java.util.AbstractMap.SimpleImmutableEntry
import java.util.Optional
import java.util.function.{ Function JFunction }
import java.util.{ Optional, List JList, Map JMap }

import akka.http.javadsl.server.Route
import akka.http.javadsl.unmarshalling.Unmarshaller
import akka.http.scaladsl.server.directives.ParameterDirectives._
import akka.http.scaladsl.server.directives.{ ParameterDirectives D }

import scala.collection.JavaConverters._
import scala.compat.java8.OptionConverters._

import akka.http.javadsl.server.Route
import akka.http.scaladsl.server.directives.{ ParameterDirectives D }
import akka.http.scaladsl.server.directives.ParameterDirectives._
import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers._

abstract class ParameterDirectives extends MiscDirectives {

def parameter(name: String, inner: java.util.function.Function[String, Route]): Route = RouteAdapter(
Expand All @@ -32,6 +29,14 @@ abstract class ParameterDirectives extends MiscDirectives {
inner.apply(value.asJava).delegate
})

@CorrespondsTo("parameter")
def parameterRequiredValue[T](t: Unmarshaller[String, T], requiredValue: T, name: String, inner: java.util.function.Supplier[Route]): Route = {
import t.asScala
RouteAdapter(
D.parameter(name.as[T].!(requiredValue)) { inner.get.delegate }
)
}

@CorrespondsTo("parameterSeq")
def parameterList(name: String, inner: java.util.function.Function[java.util.List[String], Route]): Route = RouteAdapter(
D.parameter(_string2NR(name).*) { values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ final case class SchemeRejection(supported: String)
final case class MissingQueryParamRejection(parameterName: String)
extends jserver.MissingQueryParamRejection with Rejection

/**
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter value was not equal to required one.
*/
final case class InvalidRequiredValueForQueryParamRejection(parameterName: String, expectedValue: String, actualValue: String)
extends jserver.InvalidRequiredValueForQueryParamRejection with Rejection

/**
* Rejection created by parameter filters.
* Signals that the request was rejected because a query parameter could not be interpreted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ object RejectionHandler {
case MissingQueryParamRejection(paramName)
rejectRequestEntityAndComplete((NotFound, "Request is missing required query parameter '" + paramName + '\''))
}
.handle {
case InvalidRequiredValueForQueryParamRejection(paramName, requiredValue, _)
rejectRequestEntityAndComplete((NotFound, s"Request is missing required value '$requiredValue' for query parameter '$paramName'"))
}
.handle {
case RequestEntityExpectedRejection
rejectRequestEntityAndComplete((BadRequest, "Request entity expected but not supplied"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ object ParameterDirectives extends ParameterDirectives {
import ctx.materializer
onComplete(fsou(ctx.request.uri.query().get(paramName))) flatMap {
case Success(value) if value == requiredValue pass
case _ reject
case Success(value) reject(InvalidRequiredValueForQueryParamRejection(paramName, requiredValue.toString, value.toString))
case _ reject(MissingQueryParamRejection(paramName))
}
}
implicit def forRVR[T](implicit fsu: FSU[T]): ParamDefAux[RequiredValueReceptacle[T], Directive0] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,15 @@ Java

@@snip [ParameterDirectivesExamplesSpec.scala]($test$/scala/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala) { #optional-with-default }

@@@

### Parameter with required value

@@snip [ParameterDirectivesExamplesSpec.scala]($test$/scala/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala) { #required-value }
Scala
: @@snip [ParameterDirectivesExamplesSpec.scala]($test$/scala/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala) { #required-value }

@@@
Java
: @@snip [ParameterDirectivesExamplesTest.java]($test$/java/docs/http/javadsl/server/directives/ParameterDirectivesExamplesTest.java) { #required-value }

### Deserialized parameter

Expand Down
Loading

0 comments on commit fd94b5d

Please sign in to comment.