Skip to content

Commit

Permalink
Merge pull request finagle#372 from finagle/vkostyukov/tail-extractors
Browse files Browse the repository at this point in the history
Refactor tail extractors
  • Loading branch information
vkostyukov committed Aug 4, 2015
2 parents b1721f7 + 8740ba0 commit a13609f
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 22 deletions.
57 changes: 42 additions & 15 deletions core/src/main/scala/io/finch/route/Extractor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,72 @@ import com.twitter.util.{Future, Try}
/**
* An universal extractor that extracts some value of type `A` if it's possible to fetch the value from the string.
*/
case class Extractor[A](name: String, f: String => A) extends Router[A] { self =>
case class Extractor[A](name: String, f: String => A) extends Router[A] {
import Router._
def apply(input: Input): Option[(Input, () => Future[A])] =
for {
ss <- input.headOption
aa <- Try(f(ss)).toOption
} yield (input.drop(1), () => Future.value(aa))

def apply(n: String): Extractor[A] = copy[A](name = n)
def apply(n: String): Router[A] = copy[A](name = n)

def * : Router[Seq[A]] = new Router[Seq[A]] {
override def apply(input: Input): Option[(Input, () => Future[Seq[A]])] =
Some((input.copy(path = Nil), () => Future.value(for {
s <- input.path
a <- Try(self.f(s)).toOption
} yield a)))
override def toString: String = s":$name"
}

/**
* An extractor that extracts a value of type `Seq[A]` from the tail of the route.
*/
case class TailExtractor[A](name: String, f: String => A) extends Router[Seq[A]] {
import Router._
def apply(input: Input): Option[(Input, () => Future[Seq[A]])] =
Some((input.copy(path = Nil), () => Future.value(for {
s <- input.path
a <- Try(f(s)).toOption
} yield a)))

override def toString = self.toString + ".*"
}
def apply(n: String): Router[Seq[A]] = copy[A](name = n)

override def toString: String = s":$name"
override def toString: String = s":$name*"
}

/**
* A [[io.finch.route.Router Router]] that extract an integer from the route.
* A [[Router]] that extract an integer value from the route.
*/
object int extends Extractor("int", _.toInt)

/**
* A [[io.finch.route.Router Router]] that extract a long value from the route.
* A [[Router]] that extract an integer tail from the route.
*/
object ints extends TailExtractor("int", _.toInt)

/**
* A [[Router]] that extract a long value from the route.
*/
object long extends Extractor("long", _.toLong)

/**
* A [[io.finch.route.Router Router]] that extract a string value from the route.
* A [[Router]] that extract a long tail from the route.
*/
object longs extends TailExtractor("long", _.toLong)

/**
* A [[Router]] that extract a string value from the route.
*/
object string extends Extractor("string", identity)

/**
* A [[io.finch.route.Router Router]] that extract a boolean value from the route.
* A [[Router]] that extract a string tail from the route.
*/
object strings extends TailExtractor("string", identity)

/**
* A [[Router]] that extract a boolean value from the route.
*/
object boolean extends Extractor("boolean", _.toBoolean)

/**
* A [[Router]] that extract a boolean tail from the route.
*/
object booleans extends TailExtractor("boolean", _.toBoolean)

14 changes: 7 additions & 7 deletions core/src/test/scala/io/finch/route/RouterSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,15 @@ class RouterSpec extends FlatSpec with Matchers with Checkers {
}

it should "extract the tail of the route" in {
val r: Router[Seq[String]] = get("a" / string.*)
val r: Router[Seq[String]] = get("a" / strings)
runRouter(r, route) shouldBe Some((route.drop(4), Seq("1", "b", "2")))
}

it should "extract ints from the tail of the route" in {
val route1 = Input(Request("/a/1/4/42"))

val r1: Router[Seq[Int]] = get("a" / int.*)
val r2: Router[Int :: Seq[Int] :: HNil] = get("a" / int("1") / int.*)
val r1: Router[Seq[Int]] = get("a" / ints)
val r2: Router[Int :: Seq[Int] :: HNil] = get("a" / int("1") / ints)

runRouter(r1, route1) shouldBe Some((route1.drop(4), Seq(1, 4, 42)))
runRouter(r1, route) shouldBe Some((route.drop(4), Seq(1, 2)))
Expand All @@ -148,13 +148,13 @@ class RouterSpec extends FlatSpec with Matchers with Checkers {

it should "extract booleans from the tail of the route" in {
val route = Input(Request("/flag/true/false/true"))
val r: Router[Seq[Boolean]] = get("flag" / boolean.*)
val r: Router[Seq[Boolean]] = get("flag" / booleans)
runRouter(r, route) shouldBe Some((route.drop(4), Seq(true, false, true)))
}

it should "extract the tail of the route in case it's empty" in {
val route = Input(Request("/a"))
val r: Router[Seq[String]] = get("a" / string.*)
val r: Router[Seq[String]] = get("a" / strings)
runRouter(r, route) shouldBe Some((route.drop(4), Nil))
}

Expand All @@ -164,14 +164,14 @@ class RouterSpec extends FlatSpec with Matchers with Checkers {
val r3: Router3[Int, Long, String] = get(("a" | "b") / int / long / string)
val r4: Router3[String, Int, Boolean] = get(string("name") / int("id") / boolean("flag") / "foo")
val r5: Router0 = post(*)
val r6: Router[Seq[String]] = post(string.*)
val r6: Router[Seq[String]] = post(strings)

r1.toString shouldBe "GET /"
r2.toString shouldBe "GET /a/true/1"
r3.toString shouldBe "GET /(a|b)/:int/:long/:string"
r4.toString shouldBe "GET /:name/:id/:flag/foo"
r5.toString shouldBe "POST /*"
r6.toString shouldBe "POST /:string.*"
r6.toString shouldBe "POST /:string*"
}

it should "support the for-comprehension syntax" in {
Expand Down

0 comments on commit a13609f

Please sign in to comment.