loicdescotte
7/2/2014 - 7:50 AM

Simple Play routing DSL with string interpolation

Simple Play routing DSL with string interpolation

import java.util.regex.Pattern
import play.core.Routes
import play.api.mvc._

object Router extends Routes {

  def routes = {
    // Static paths
    case Route("GET",    p"")              => controllers.Application.index
    case Route("GET",    p"/items")        => controllers.Items.list

    // A simple parameter
    case Route("GET",    p"/item/$id")     => controllers.Items.get(id)
    case Route("DELETE", p"/item/$id")     => controllers.Items.delete(id)

    // Non string parameters?
    case Route("GET",    p"/item/$id/child/${Id(child)}") => controllers.Items.getChild(id, child: Long)

    // Regexes
    case Route("GET",    p"/item/$id/part/$part<[A-Z]>")  => controllers.Items.getPart(id, part)

    // multiple path part parameters
    case Route("GET",    p"/assets/$file*") => controllers.Assets.versioned(path = "/public", file: Asset)
  }

  // The magic is implemented here...

  // A path extracting String interpolator
  implicit class PathContext(sc: StringContext) {
    val p = {
      // "parse" the path
      sc.parts.tail.map { part =>
        if (part.startsWith("*")) {
          // It's a .* matcher
          "(.*)" + Pattern.quote(part.drop(1))
        } else if (part.startsWith("<") && part.contains(">")) {
          // It's a regex matcher
          val splitted = part.split(">", 2)
          val regex = splitted(0).drop(1)
          "(" + regex + ")" + Pattern.quote(splitted(1))
        } else {
          // It's an ordinary path part matcher
          "([^/]*)" + Pattern.quote(part)
        }
      }.mkString(Pattern.quote(sc.parts.head), "", "/?").r
    }
  }

  // Extractor for routes
  object Route {
    def unapply(rh: RequestHeader) = {
      if (rh.path.startsWith(prefix) {
        Some(rh.method -> rh.path.drop(prefix.length))
      } else None
    }
  }

  // Extractor for Long ids
  object Id {
    def unapply(s: String) = try {
      Some(s.toLong)
    } catch {
      case e: NumberFormatException => None
    }
  }

  // routes boiler plate fluff
  private var _prefix = ""
  def prefix = _prefix
  def setPrefix(prefix: String) = _prefix = prefix
  def documentation = Nil  
}