Created
November 29, 2016 20:05
-
-
Save vaclavsvejcar/a33711fa34cb71a6c7492075218c5ed4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.norcane.reelife.client.router | |
import scala.annotation.tailrec | |
import scala.language.implicitConversions | |
package object syntax { | |
type Out = String | |
def bind[R](routeDef: RouteDef[R]): R = routeDef.self | |
protected def splitPath(path: String): List[String] = path.split("/").dropWhile(_.isEmpty).toList | |
sealed trait PathElem | |
case class Static(name: String) extends PathElem { | |
override def toString: String = name | |
} | |
case object * extends PathElem | |
case object ** extends PathElem | |
sealed trait RouteDef[Self] { | |
def self: Self | |
def elems: List[PathElem] | |
} | |
implicit def stringToRouteDef0(name: String): RouteDef0 = RouteDef0(Static(name) :: Nil) | |
implicit def asterixToRoutePath1(ast: *.type): RouteDef1 = RouteDef1(ast :: Nil) | |
implicit def stringToStatic(name: String): Static = Static(name) | |
case class RouteDef0(elems: List[PathElem]) extends RouteDef[RouteDef0] { | |
def /(static: Static) = RouteDef0(elems :+ static) | |
def /(p: PathElem) = RouteDef1(elems :+ p) | |
def self = RouteDef0(elems) | |
def to(f0: () ⇒ Out) = Route0(this, f0) | |
} | |
case class RouteDef1(elems: List[PathElem]) extends RouteDef[RouteDef1] { | |
def /(static: Static) = RouteDef1(elems :+ static) | |
def /(p: PathElem) = RouteDef2(elems :+ p) | |
def self = RouteDef1(elems) | |
def to[A: PathParam : Manifest](f1: (A) ⇒ Out) = Route1(this, f1) | |
} | |
case class RouteDef2(elems: List[PathElem]) extends RouteDef[RouteDef2] { | |
def /(static: Static) = RouteDef2(elems :+ static) | |
def self = RouteDef2(elems) | |
def to[A: PathParam : Manifest, B: PathParam : Manifest](f2: (A, B) ⇒ Out) = Route2(this, f2) | |
} | |
sealed trait Route[RD] { | |
def routeDef: RouteDef[RD] | |
def unapply(path: String): Option[() => Out] | |
} | |
case class Route0(routeDef: RouteDef0, f0: () ⇒ Out) extends Route[RouteDef0] { | |
def apply(): String = PathMatcher0(routeDef.elems)() | |
override def unapply(path: String): Option[() => Out] = | |
PathMatcher0.unapply(routeDef.elems, splitPath(path), f0) | |
} | |
case class Route1[A: PathParam : Manifest](routeDef: RouteDef1, f1: (A) ⇒ Out) extends Route[RouteDef1] { | |
def apply(a: A): String = PathMatcher1(routeDef.elems)(a) | |
override def unapply(path: String): Option[() => Out] = | |
PathMatcher1.unapply(routeDef.elems, splitPath(path), f1) | |
} | |
case class Route2[A: PathParam : Manifest, B: PathParam : Manifest](routeDef: RouteDef2, f2: (A, B) ⇒ Out) extends Route[RouteDef2] { | |
def apply(a: A, b: B): String = PathMatcher2(routeDef.elems)(a, b) | |
override def unapply(path: String): Option[() => Out] = | |
PathMatcher2.unapply(routeDef.elems, splitPath(path), f2) | |
} | |
trait PathParam[T] { | |
def apply(t: T): String | |
def unapply(s: String): Option[T] | |
} | |
implicit val StringPathParam: PathParam[String] = new PathParam[String] { | |
def apply(s: String): String = s | |
def unapply(s: String): Option[String] = Some(s) | |
} | |
implicit val BooleanPathParam: PathParam[Boolean] = new PathParam[Boolean] { | |
def apply(b: Boolean): String = b.toString | |
def unapply(s: String): Option[Boolean] = s.toLowerCase match { | |
case "1" | "true" | "yes" ⇒ Some(true) | |
case "0" | "false" | "no" ⇒ Some(false) | |
case _ ⇒ None | |
} | |
} | |
object PathMatcher0 { | |
def apply(elems: List[PathElem])(): String = elems.mkString("/", "/", "") | |
def unapply(elems: List[PathElem], parts: List[String], handler: () => Out): Option[() => Out] = | |
(elems, parts) match { | |
case (Nil, Nil) => Some(handler) | |
case (Static(x) :: xs, y :: ys) if x == y => unapply(xs, ys, handler) | |
case _ => None | |
} | |
} | |
object PathMatcher1 { | |
def apply[A](elems: List[PathElem], prefix: List[PathElem] = Nil)(a: A) | |
(implicit ppa: PathParam[A]): String = elems match { | |
case Static(x) :: rest => apply(rest, prefix :+ Static(x))(a) | |
case (* | **) :: rest => PathMatcher0(prefix ::: Static(ppa(a)) :: rest)() | |
case _ => PathMatcher0(elems)() | |
} | |
@tailrec | |
def unapply[A](elems: List[PathElem], parts: List[String], handler: (A) => Out) | |
(implicit ppa: PathParam[A]): Option[() => Out] = (elems, parts) match { | |
case (Static(x) :: xs, y :: ys) if x == y => unapply(xs, ys, handler) | |
case (* :: xs, ppa(a) :: ys) => PathMatcher0.unapply(xs, ys, () => handler(a)) | |
case (** :: xs, ys) => ppa.unapply(ys.mkString("/")).map { a => () => handler(a) } | |
case _ => None | |
} | |
} | |
object PathMatcher2 { | |
def apply[A, B](elems: List[PathElem], prefix: List[PathElem] = Nil)(a: A, b: B) | |
(implicit ppa: PathParam[A], ppb: PathParam[B]): String = elems match { | |
case Static(x) :: rest => apply(rest, prefix :+ Static(x))(a, b) | |
case (* | **) :: rest => PathMatcher1(prefix ::: Static(ppa(a)) :: rest)(b) | |
case _ => PathMatcher0(elems)() | |
} | |
@tailrec | |
def unapply[A, B](elems: List[PathElem], parts: List[String], handler: (A, B) => Out) | |
(implicit ppa: PathParam[A], ppb: PathParam[B]): Option[() => Out] = | |
(elems, parts) match { | |
case (Static(x) :: xs, y :: ys) if x == y => unapply(xs, ys, handler) | |
case (* :: xs, ppa(a) :: ys) => PathMatcher1.unapply(xs, ys, (b: B) => handler(a, b)) | |
case _ => None | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment