src/router.bend source
src/router.bend on the hub · documented module
import Baseimport ./server.bend as Webimport ./app.bend as Apptype Param is Data: Param{name: String, value: String}type Match is Data: Matched{params: List<&2, Param>} NoMatch{}def prepend(name: String, value: String, result: Match) -> Match: match result: case Matched{params}: Matched{Param{name, value} <> params} case NoMatch{}: NoMatch{}def literal(same: Bool, next: Unit -> Match) -> Match: match same: case True{}: next(Unit{}) case False{}: NoMatch{}def capture(parameter: Bool, pattern: String, +name: String, +value: String, next: Unit -> Match) -> Match: match parameter: case True{}: literal(Bool.and(Bool.not(String.is_empty(name)), Bool.not(String.is_empty(value))), u => prepend(name, value, next(Unit{}))) case False{}: literal(String.eq(pattern, value), next)def segment(pattern: String, +value: String, next: Unit -> Match) -> Match: match pattern: case SNil{}: literal(String.is_empty(value), next) case SCon{+head, +tail}: capture(Char.is_eq(head, ':'), SCon{head, tail}, tail, value, next)def values(patterns: List<&2, String>, path: List<&2, String>) -> Match: match patterns path: case Nil{} Nil{}: Matched{Nil{}} case Con{pattern, tail} Con{head, rest}: segment(pattern, head, u => values(tail, rest)) case other other_path: NoMatch{}def match_path(pattern: String, path: String) -> Match: values(String.split(pattern, '/'), String.split(path, '/'))def param_step(found: Bool, value: String, next: Unit -> Maybe<String>) -> Maybe<String>: match found: case True{}: Some{value} case False{}: next(Unit{})def param(+name: String, params: List<&2, Param>) -> Maybe<String>: match params: case Nil{}: None{} case Con{Param{key, value}, tail}: param_step(String.eq(name, key), value, u => param(name, tail))type Route is Type: Route{method: String, pattern: String, handler: List<&2, Param> -> Web.Incoming -> IO(Web.Reply)}type Selection is Type: Found{params: List<&2, Param>, handler: List<&2, Param> -> Web.Incoming -> IO(Web.Reply)} Missing{} WrongMethod{methods: List<&2, String>}def add_allowed(+method: String, result: Selection) -> Selection: match result: case Found{params, handler}: Found{params, handler} case Missing{}: WrongMethod{[method]} case WrongMethod{+methods}: WrongMethod{App.unique(App.contains(method, methods), method, methods)}def chosen(same: Bool, method: String, params: List<&2, Param>, handler: List<&2, Param> -> Web.Incoming -> IO(Web.Reply), next: Unit -> Selection) -> Selection: match same: case True{}: Found{params, handler} case False{}: add_allowed(method, next(Unit{}))def matched(result: Match, same: Bool, method: String, handler: List<&2, Param> -> Web.Incoming -> IO(Web.Reply), next: Unit -> Selection) -> Selection: match result: case Matched{params}: chosen(same, method, params, handler, next) case NoMatch{}: next(Unit{})def select(routes: List<Route>, +method: String, +path: String) -> Selection: match routes: case Nil{}: Missing{} case Con{Route{+verb, pattern, handler}, tail}: matched(match_path(pattern, path), String.eq(verb, method), verb, handler, u => select(tail, method, path))def selected(result: Selection, request: Web.Incoming) -> IO(Web.Reply): match result: case Found{params, handler}: handler(params, request) case Missing{}: App.error(404, "not_found", "No route matches this path") case WrongMethod{methods}: do IO<Web.Reply>: reply : Web.Reply <- App.error(405, "method_not_allowed", "Method not allowed for this path") IO.pure(Web.Reply, Web.with_header("Allow", App.allowed(methods), reply))def route(routes: List<Route>, request: Web.Incoming) -> IO(Web.Reply): match request: case Web.Incoming{id, +method, +path, target, headers, body}: selected(select(routes, method, path), Web.Incoming{id, method, path, target, headers, body})