From 6af0bb7aa231c41213b455e1ae44a50a5b295292 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Thu, 14 Feb 2013 17:39:46 -0600 Subject: implement uriFor --- src/main/scala/router.scala | 51 +++++++++++++++++++++++++++++++++++++++++++-- src/test/scala/basic.scala | 1 + 2 files changed, 50 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main/scala/router.scala b/src/main/scala/router.scala index a9b2ec1..56e0342 100644 --- a/src/main/scala/router.scala +++ b/src/main/scala/router.scala @@ -42,8 +42,55 @@ class Router[T] { _route(path.split("/"), routes.toList) } - def uriFor (mapping: Map[String, String]): String = { - throw new Error("unimplemented") + private class AmbiguousRouteMapping( + mapping: Map[String, String], + paths: Seq[String] + ) extends RuntimeException { + override def getMessage (): String = { + "Ambiguous path descriptor (specified keys " + + mapping.keys.mkString(", ") + + "): could match paths " + + paths.mkString(", ") + } + } + + def uriFor (mapping: Map[String, String]): Option[String] = { + // first remove all routes that can't possibly match + // - if the route requires a variable component that doesn't exist in the + // mapping, then it can't match + // - if the route contains a value for a variable component that doesn't + // pass the validation for that component, it can't match + // - if the route contains a default value, and that component also exists + // in the mapping, then the values must match + val possible = routes.flatMap(r => { + r.pathWithMapping(mapping) match { + case Some(path) => Some(r -> path) + case None => None + } + }) + + possible.toList match { + case Nil => None + case (r, path) :: Nil => Some(path) + case rs => { + // then try to disambiguate the remaining possibilities + // - we want the route with the fewest number of "extra" items in the + // mapping, after removing defaults and variable path components + val possibleByRemainder = possible.groupBy { case (r, path) => { + (mapping.keys.toSet -- + r.defaults.keys.toSet -- + r.requiredVariableComponents -- + r.optionalVariableComponents).size + } } + val found = possibleByRemainder(possibleByRemainder.keys.min) + found.toList match { + case Nil => None + case (r, path) :: Nil => Some(path) + case rs => + throw new AmbiguousRouteMapping(mapping, rs.map(_._1.path)) + } + } + } } } diff --git a/src/test/scala/basic.scala b/src/test/scala/basic.scala index 070ace8..ef9ea22 100644 --- a/src/test/scala/basic.scala +++ b/src/test/scala/basic.scala @@ -57,6 +57,7 @@ class Basic extends FunSuite { ) def testRoute (router: Router[Boolean], path: String, mapping: Map[String, String]) { + assert(path === router.uriFor(mapping).get) val om = router.route(path) assert(om.isDefined) val m = om.get -- cgit v1.2.3-54-g00ecf