Skip to content

Commit

Permalink
Path: Fix parsing of empty query arguments
Browse files Browse the repository at this point in the history
Also fix custom codec in manual which did not handle empty sets.
  • Loading branch information
tindzk committed May 24, 2019
1 parent 3abddf1 commit 8b08a9e
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 13 deletions.
4 changes: 2 additions & 2 deletions manual/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ Create a routing table:
Note that you can populate the `trail.Path()` data structure yourself and use it in place of its string counterpart. This is useful if you are using a third-party routing library which already provides you with a deconstructed URL:
<listing id="parse-path">

Define a custom argument type:
<listing id="custom-arg">
Define a custom codec that can be used in arguments, parameters and fragments:
<listing id="custom-codec">

Define a custom path element type:
<listing id="custom-path-elem">
10 changes: 5 additions & 5 deletions manual/listings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
"language" : "scala",
"result" : "user: hello, show: false"
},
"custom-arg" : {
"code" : "import scala.util.Try\nimplicit case object IntSetArg extends Codec[Set[Int]] {\n override def encode(s: Set[Int]): Option[String] = Some(s.mkString(\",\"))\n override def decode(s: Option[String]): Option[Set[Int]] =\n s.flatMap(value => Try(value.split(',').map(_.toInt).toSet).toOption)\n}\n\nval export = Root / \"export\" / Arg[Set[Int]]\nprintln(export.url(Set(1, 2, 3)))",
"language" : "scala",
"result" : "/export/1,2,3"
},
"query-params-opt" : {
"code" : "val routeParamsOpt = Root / \"details\" & Param[Int](\"id\") & Param[Option[Boolean]](\"show\")\nprintln(routeParamsOpt.parse(\"/details?id=42\"))",
"language" : "scala",
Expand All @@ -44,6 +39,11 @@
"language" : "scala",
"result" : "/asdf"
},
"custom-codec" : {
"code" : "import scala.util.Try\nimplicit case object IntSetCodec extends Codec[Set[Int]] {\n override def encode(s: Set[Int]): Option[String] = Some(s.mkString(\",\"))\n override def decode(s: Option[String]): Option[Set[Int]] =\n s.flatMap(value =>\n if (value.isEmpty) Some(Set())\n else Try(value.split(',').map(_.toInt).toSet).toOption)\n}\n\nval export = Root / \"export\" / Arg[Set[Int]]\nprintln(export.url(Set(1, 2, 3)))",
"language" : "scala",
"result" : "/export/1,2,3"
},
"query-fragment" : {
"code" : "val routeFragment = Root $ Fragment[Int]\nprintln(routeFragment.parse(\"/#42\"))",
"language" : "scala",
Expand Down
8 changes: 5 additions & 3 deletions manual/src/main/scala/trail/manual/Listings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ object Listings extends App {
}
println(result2)

listing("custom-arg")
listing("custom-codec")
import scala.util.Try
implicit case object IntSetArg extends Codec[Set[Int]] {
implicit case object IntSetCodec extends Codec[Set[Int]] {
override def encode(s: Set[Int]): Option[String] = Some(s.mkString(","))
override def decode(s: Option[String]): Option[Set[Int]] =
s.flatMap(value => Try(value.split(',').map(_.toInt).toSet).toOption)
s.flatMap(value =>
if (value.isEmpty) Some(Set())
else Try(value.split(',').map(_.toInt).toSet).toOption)
}

val export = Root / "export" / Arg[Set[Int]]
Expand Down
6 changes: 3 additions & 3 deletions shared/src/main/scala/trail/Path.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ object PathParser {

def parseArgs(query: String): List[(String, String)] =
query.split('&').flatMap { x =>
val pair = x.split('=')
if (pair.length != 2) List.empty
else List((pair(0), URI.decode(pair(1))))
val equalSign = x.indexOf('=')
if (equalSign == -1) List()
else List((x.take(equalSign), URI.decode(x.drop(equalSign + 1))))
}.toList

/** Return URL without scheme and authority */
Expand Down
6 changes: 6 additions & 0 deletions shared/src/test/scala/trail/PathTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@ class PathTests extends FunSuite {
assert(parsed.url == url)
}
}

test("Parse argument with empty value") {
val url = "/test?a="
assert(PathParser.parse(url).path == "/test")
assert(PathParser.parse(url).args == List("a" -> ""))
}
}
17 changes: 17 additions & 0 deletions shared/src/test/scala/trail/RouteParseTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package trail

import org.scalatest._

import scala.util.Try

class RouteParseTests extends FunSpec with Matchers {
it("Parse root") {
val root = Root
Expand Down Expand Up @@ -58,4 +60,19 @@ class RouteParseTests extends FunSpec with Matchers {
assert(userInfo.parse("/user/hello/false")
.contains(("hello", false)))
}

it("Set codec") {
implicit case object LongSetCodec extends Codec[Set[Long]] {
override def encode(s: Set[Long]): Option[String] = Some(s.mkString(","))
override def decode(s: Option[String]): Option[Set[Long]] =
s.flatMap(value =>
if (value.isEmpty) Some(Set())
else Try(value.split(',').map(_.toLong).toSet).toOption)
}

val route = Root / "size" & Param[Set[Long]]("folders")
assert(route.parse(route(Set[Long]())).contains(Set[Long]()))
assert(route.parse(route(Set[Long](1))).contains(Set[Long](1)))
assert(route.parse(route(Set[Long](1, 2, 3))).contains(Set[Long](1, 2, 3)))
}
}

0 comments on commit 8b08a9e

Please sign in to comment.