diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala index 036bbb2c12..6824e3c03e 100755 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala @@ -192,13 +192,16 @@ class ParameterDirectivesExamplesSpec extends RoutingSpec with PredefinedFromStr responseAs[String] shouldEqual "The parameters are x = '1', x = '2'" } } - "csv string sequence" in { + "csv" in { val route = - parameter("names".as(CsvStringSeq)) { names => + parameter("names".as(CsvSeq[String])) { names => complete(s"The parameters are ${names.mkString(", ")}") } // tests: + Get("/?names=") ~> route ~> check { + responseAs[String] shouldEqual "The parameters are " + } Get("/?names=Caplin") ~> route ~> check { responseAs[String] shouldEqual "The parameters are Caplin" } @@ -206,48 +209,4 @@ class ParameterDirectivesExamplesSpec extends RoutingSpec with PredefinedFromStr responseAs[String] shouldEqual "The parameters are Caplin, John" } } - "csv byte sequence" in { - val route = - parameter("numbers".as(CsvByteSeq)) { bytes => - complete(s"The numbers are ${bytes.mkString(", ")}") - } - - // tests: - Get(s"/?numbers=2,${Byte.MaxValue}") ~> route ~> check { - responseAs[String] shouldEqual s"The numbers are 2, ${Byte.MaxValue}" - } - } - "csv short sequence" in { - val route = - parameter("numbers".as(CsvShortSeq)) { shorts => - complete(s"The numbers are ${shorts.mkString(", ")}") - } - - // tests: - Get(s"/?numbers=2,${Short.MaxValue}") ~> route ~> check { - responseAs[String] shouldEqual s"The numbers are 2, ${Short.MaxValue}" - } - } - "csv int sequence" in { - val route = - parameter("numbers".as(CsvIntSeq)) { ints => - complete(s"The numbers are ${ints.mkString(", ")}") - } - - // tests: - Get(s"/?numbers=2,${Int.MaxValue}") ~> route ~> check { - responseAs[String] shouldEqual s"The numbers are 2, ${Int.MaxValue}" - } - } - "csv long sequence" in { - val route = - parameter("numbers".as(CsvLongSeq)) { longs => - complete(s"The numbers are ${longs.mkString(", ")}") - } - - // tests: - Get(s"/?numbers=2,${Long.MaxValue}") ~> route ~> check { - responseAs[String] shouldEqual s"The numbers are 2, ${Long.MaxValue}" - } - } } diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst index 771e5394b5..cdc32f684c 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/parameter-directives/parameters.rst @@ -94,8 +94,8 @@ Repeated parameter .. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala :snippet: repeated -Csv valued parameter -^^^^^^^^^^^^^^^^^^^^ +CSV parameter +^^^^^^^^^^^^^ .. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/ParameterDirectivesExamplesSpec.scala :snippet: csv diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/ParameterDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/ParameterDirectivesSpec.scala index c4332e0f00..5dc6a5cfc8 100755 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/ParameterDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/ParameterDirectivesSpec.scala @@ -6,8 +6,7 @@ package akka.http.scaladsl.server package directives import org.scalatest.{ FreeSpec, Inside } -import akka.http.scaladsl.unmarshalling.Unmarshaller.HexInt -import akka.http.scaladsl.unmarshalling.Unmarshaller.CsvStringSeq +import akka.http.scaladsl.unmarshalling.Unmarshaller._ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Inside { "when used with 'as[Int]' the parameter directive should" - { @@ -33,17 +32,17 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi "create typed optional parameters that" - { "extract Some(value) when present" in { Get("/?amount=12") ~> { - parameter("amount".as[Int]?) { echoComplete } + parameter("amount".as[Int].?) { echoComplete } } ~> check { responseAs[String] shouldEqual "Some(12)" } } "extract None when not present" in { Get() ~> { - parameter("amount".as[Int]?) { echoComplete } + parameter("amount".as[Int].?) { echoComplete } } ~> check { responseAs[String] shouldEqual "None" } } "cause a MalformedQueryParamRejection on illegal Int values" in { Get("/?amount=x") ~> { - parameter("amount".as[Int]?) { echoComplete } + parameter("amount".as[Int].?) { echoComplete } } ~> check { inside(rejection) { case MalformedQueryParamRejection("amount", "'x' is not a valid 32-bit signed integer value", Some(_)) ⇒ @@ -53,9 +52,9 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi } } - "when used with 'as(CsvStringSeq)' the parameter directive should" - { + "when used with 'as(CsvSeq[...])' the parameter directive should" - { val route = - parameter("names".as(CsvStringSeq)) { names ⇒ + parameter("names".as(CsvSeq[String])) { names ⇒ complete(s"The parameters are ${names.mkString(", ")}") } @@ -94,17 +93,17 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi "create typed optional parameters that" - { "extract Some(value) when present" in { Get("/?amount=A") ~> { - parameter("amount".as(HexInt)?) { echoComplete } + parameter("amount".as(HexInt).?) { echoComplete } } ~> check { responseAs[String] shouldEqual "Some(10)" } } "extract None when not present" in { Get() ~> { - parameter("amount".as(HexInt)?) { echoComplete } + parameter("amount".as(HexInt).?) { echoComplete } } ~> check { responseAs[String] shouldEqual "None" } } "cause a MalformedQueryParamRejection on illegal Int values" in { Get("/?amount=x") ~> { - parameter("amount".as(HexInt)?) { echoComplete } + parameter("amount".as(HexInt).?) { echoComplete } } ~> check { inside(rejection) { case MalformedQueryParamRejection("amount", "'x' is not a valid 32-bit hexadecimal integer value", Some(_)) ⇒ @@ -148,8 +147,8 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi } ~> check { responseAs[String] shouldEqual "EllenParsons" } } "correctly extract an optional parameter" in { - Get("/?foo=bar") ~> parameters('foo ?) { echoComplete } ~> check { responseAs[String] shouldEqual "Some(bar)" } - Get("/?foo=bar") ~> parameters('baz ?) { echoComplete } ~> check { responseAs[String] shouldEqual "None" } + Get("/?foo=bar") ~> parameters('foo.?) { echoComplete } ~> check { responseAs[String] shouldEqual "Some(bar)" } + Get("/?foo=bar") ~> parameters('baz.?) { echoComplete } ~> check { responseAs[String] shouldEqual "None" } } "ignore additional parameters" in { Get("/?name=Parsons&FirstName=Ellen&age=29") ~> { @@ -167,7 +166,7 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi } "supply the default value if an optional parameter is missing" in { Get("/?name=Parsons&FirstName=Ellen") ~> { - parameters("name"?, 'FirstName, 'age ? "29", 'eyes?) { (name, firstName, age, eyes) ⇒ + parameters("name".?, 'FirstName, 'age ? "29", 'eyes.?) { (name, firstName, age, eyes) ⇒ complete(firstName + name + age + eyes) } } ~> check { responseAs[String] shouldEqual "EllenSome(Parsons)29None" } @@ -214,12 +213,12 @@ class ParameterDirectivesSpec extends FreeSpec with GenericRoutingSpec with Insi } "extract as Iterable[Int]" in { Get("/person?age=19&number=3&number=5") ~> { - parameter('number.as[Int]*) { echoComplete } + parameter('number.as[Int].*) { echoComplete } } ~> check { responseAs[String] === "List(3, 5)" } } "extract as Iterable[Int] with an explicit deserializer" in { Get("/person?age=19&number=3&number=A") ~> { - parameter('number.as(HexInt)*) { echoComplete } + parameter('number.as(HexInt).*) { echoComplete } } ~> check { responseAs[String] === "List(3, 10)" } } } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala b/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala index ff0b635796..5776ef36b9 100755 --- a/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala @@ -5,56 +5,27 @@ package akka.http.scaladsl.unmarshalling import scala.collection.immutable +import akka.http.scaladsl.util.FastFuture trait PredefinedFromStringUnmarshallers { implicit val byteFromStringUnmarshaller: Unmarshaller[String, Byte] = - Unmarshaller.strict(byteFromString) + numberUnmarshaller(_.toByte, "8-bit signed integer") implicit val shortFromStringUnmarshaller: Unmarshaller[String, Short] = - Unmarshaller.strict(shortFromString) + numberUnmarshaller(_.toShort, "16-bit signed integer") implicit val intFromStringUnmarshaller: Unmarshaller[String, Int] = - Unmarshaller.strict(intFromString) + numberUnmarshaller(_.toInt, "32-bit signed integer") implicit val longFromStringUnmarshaller: Unmarshaller[String, Long] = - Unmarshaller.strict(longFromString) - - val HexByte: Unmarshaller[String, Byte] = - Unmarshaller.strict[String, Byte] { string ⇒ - try java.lang.Byte.parseByte(string, 16) - catch numberFormatError(string, "8-bit hexadecimal integer") - } - - val HexShort: Unmarshaller[String, Short] = - Unmarshaller.strict[String, Short] { string ⇒ - try java.lang.Short.parseShort(string, 16) - catch numberFormatError(string, "16-bit hexadecimal integer") - } - - val HexInt: Unmarshaller[String, Int] = - Unmarshaller.strict[String, Int] { string ⇒ - try java.lang.Integer.parseInt(string, 16) - catch numberFormatError(string, "32-bit hexadecimal integer") - } - - val HexLong: Unmarshaller[String, Long] = - Unmarshaller.strict[String, Long] { string ⇒ - try java.lang.Long.parseLong(string, 16) - catch numberFormatError(string, "64-bit hexadecimal integer") - } + numberUnmarshaller(_.toLong, "64-bit signed integer") implicit val floatFromStringUnmarshaller: Unmarshaller[String, Float] = - Unmarshaller.strict[String, Float] { string ⇒ - try string.toFloat - catch numberFormatError(string, "32-bit floating point") - } + numberUnmarshaller(_.toFloat, "32-bit floating point") implicit val doubleFromStringUnmarshaller: Unmarshaller[String, Double] = - Unmarshaller.strict[String, Double] { string ⇒ - try string.toDouble - catch numberFormatError(string, "64-bit floating point") - } + numberUnmarshaller(_.toDouble, "64-bit floating point") implicit val booleanFromStringUnmarshaller: Unmarshaller[String, Boolean] = Unmarshaller.strict[String, Boolean] { string ⇒ @@ -66,38 +37,31 @@ trait PredefinedFromStringUnmarshallers { } } - val CsvStringSeq: Unmarshaller[String, immutable.Seq[String]] = + implicit def CsvSeq[T](implicit unmarshaller: Unmarshaller[String, T]): Unmarshaller[String, immutable.Seq[T]] = Unmarshaller.strict[String, immutable.Seq[String]] { string ⇒ string.split(",").toList + } flatMap { implicit ec ⇒ + implicit mat ⇒ strings ⇒ + FastFuture.sequence(strings.map(unmarshaller(_))) } - val CsvByteSeq: Unmarshaller[String, immutable.Seq[Byte]] = - CsvStringSeq.map(_.map(byteFromString)) + val HexByte: Unmarshaller[String, Byte] = + numberUnmarshaller(java.lang.Byte.parseByte(_, 16), "8-bit hexadecimal integer") - val CsvShortSeq: Unmarshaller[String, immutable.Seq[Short]] = - CsvStringSeq.map(_.map(shortFromString)) + val HexShort: Unmarshaller[String, Short] = + numberUnmarshaller(java.lang.Short.parseShort(_, 16), "16-bit hexadecimal integer") - val CsvIntSeq: Unmarshaller[String, immutable.Seq[Int]] = - CsvStringSeq.map(_.map(intFromString)) + val HexInt: Unmarshaller[String, Int] = + numberUnmarshaller(java.lang.Integer.parseInt(_, 16), "32-bit hexadecimal integer") - val CsvLongSeq: Unmarshaller[String, immutable.Seq[Long]] = - CsvStringSeq.map(_.map(longFromString)) + val HexLong: Unmarshaller[String, Long] = + numberUnmarshaller(java.lang.Long.parseLong(_, 16), "64-bit hexadecimal integer") - private def byteFromString(string: String) = - try string.toByte - catch numberFormatError(string, "8-bit signed integer") - - private def shortFromString(string: String) = - try string.toShort - catch numberFormatError(string, "16-bit signed integer") - - private def intFromString(string: String) = - try string.toInt - catch numberFormatError(string, "32-bit signed integer") - - private def longFromString(string: String) = - try string.toLong - catch numberFormatError(string, "64-bit signed integer") + private def numberUnmarshaller[T](f: String ⇒ T, target: String): Unmarshaller[String, T] = + Unmarshaller.strict[String, T] { string ⇒ + try f(string) + catch numberFormatError(string, target) + } private def numberFormatError(value: String, target: String): PartialFunction[Throwable, Nothing] = { case e: NumberFormatException ⇒