air/router.bend checks
raw source on the hub · import 0x1efee7e6292e6267db2ca5c7e267456a/air/router.bend as Router
3 imports
import Base import ./text.bend as Text import ./http.bend as Http
Types
type Seg source · line 19 · raw
Data
One segment of a route pattern. Rest is *name: it must be last and
binds the remaining path, joined with "/", possibly "".
Lit@text:String -> Seg
Param@name:String -> Seg
Rest@name:String -> Seg
type Route source · line 49 · raw
Type
slash remembers whether the pattern ended with a slash, for the
strict policy.
Route@method:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Method -> @pattern:List<&2, Seg> -> @slash:Bool -> @handler:Handler -> Route
type Slash source · line 269 · raw
Data
What a trailing slash means. Ignore (the default) treats "/a/" and
"/a" as the same path. Strict matches a route only when the request
and the pattern agree on the slash. Redirect answers 308 to the path
without the slash, query kept, and never redirects "/".
IgnoreSlash
StrictSlash
RedirectSlash
type Pick source · line 325 · raw
Type
The outcome of walking the routes. Hit is an exact match. Fallback
is a GET route found while serving HEAD, kept unless an explicit HEAD
route turns up later. Miss carries the methods routes offered for
this path, so 405 and OPTIONS can list them.
Hit@handler:Handler -> @params:Map<&2, String> -> Pick
Fallback@handler:Handler -> @params:Map<&2, String> -> @allowed:Map<&2, String> -> Pick
Miss@allowed:Map<&2, String> -> Pick
type Ask source · line 378 · raw
Data
What the walk needs from the request, computed once: its path split into segments, its method's name, and the slash and star flags.
Ask@segs:List<&2, String> -> @method:String -> @strict:Bool -> @path_slash:Bool -> @star:Bool -> Ask
Definitions
def Handler source · line 14 · raw
Type
def classify source · line 24 · raw
@+s:String -> Seg
def parse_pattern.go source · line 33 · raw
@segs:List<&2, String> -> @acc:List<&2, Seg> -> List<&2, Seg>
def parse_pattern source · line 40 · raw
@+path:String -> List<&2, Seg>
def has_slash source · line 44 · raw
@+path:String -> Bool
Whether a path ends with a slash that is not the root's.
def new source · line 52 · raw
@method:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Method -> @+path:String -> @handler:Handler -> Route
def get source · line 55 · raw
@path:String -> @handler:Handler -> Route
def post source · line 58 · raw
@path:String -> @handler:Handler -> Route
def put source · line 61 · raw
@path:String -> @handler:Handler -> Route
def delete source · line 64 · raw
@path:String -> @handler:Handler -> Route
def patch source · line 67 · raw
@path:String -> @handler:Handler -> Route
def head source · line 72 · raw
@path:String -> @handler:Handler -> Route
A HEAD route is optional: without one, HEAD runs the GET route and the server drops the body.
def options source · line 77 · raw
@path:String -> @handler:Handler -> Route
An OPTIONS route is optional: without one, OPTIONS answers 204 with the methods the path allows.
def mount.go source · line 86 · raw
@+prefix:List<&2, Seg> -> @routes:List<&1, Route> -> List<&1, Route>
Prefixes every route with prefix: "/api" + "/users/:id" is
"/api/users/:id". The prefix may hold params; a wildcard in it would
make every route under it unreachable.
def mount source · line 93 · raw
@prefix:String -> @routes:List<&1, Route> -> List<&1, Route>
def append_routes source · line 96 · raw
@xs:List<&1, Route> -> @ys:List<&1, Route> -> List<&1, Route>
def all source · line 104 · raw
@groups:List<&1, List<&1, Route>> -> List<&1, Route>
Joins route groups in order: all([pages(), mount("/api", api())]).
def seg_shape source · line 114 · raw
@seg:Seg -> String
def shape.go source · line 123 · raw
@pat:List<&2, Seg> -> @rev:String -> String
def shape source · line 132 · raw
@pat:List<&2, Seg> -> String
The shape of a pattern, params and wildcards anonymized: "/users/:/*". Two routes with the same method and shape conflict.
def shape_with_slash source · line 142 · raw
@pat:List<&2, Seg> -> @slash:Bool -> String
The shape with the pattern's trailing slash, so "/s" and "/s/" are two keys: the strict policy tells them apart, and under the others the first registered wins.
def rest_misplaced source · line 150 · raw
@pat:List<&2, Seg> -> Bool
A wildcard anywhere but last can never match.
def conflicts.note source · line 161 · raw
@acc:List<&2, String> -> @key:String -> @misplaced:Bool -> @dup:Bool -> List<&2, String>
def conflicts.go source · line 170 · raw
@routes:List<&1, Route> -> @+seen:Map<&2, String> -> @acc:List<&2, String> -> List<&2, String>
def conflicts source · line 184 · raw
@routes:List<&1, Route> -> List<&2, String>
Every problem in a route list, one line each, in list order: "duplicate route: GET /users/:" or "wildcard not last: GET /a/*/b". Empty when the list is clean. The list is consumed.
def check.report source · line 187 · raw
@problems:List<&2, String> -> IO(Unit)
def check.fin source · line 196 · raw
@problems:List<&2, String> -> @clean:Bool -> IO(Unit)
def check source · line 207 · raw
@routes:List<&1, Route> -> IO(Unit)
Prints each conflict and ends the process with code 1; returns quietly
when the routes are clean. Call it in main before serving.
def pattern_of source · line 212 · raw
@routes:List<&1, Route> -> List<&2, List<&2, Seg>>
The patterns of a route list, handlers dropped; for laws.
def bind_segment.lit source · line 222 · raw
@m:Map<&2, String> -> @same:Bool -> Maybe<&2, Map<&2, String>>
def bind_segment source · line 230 · raw
@m:Map<&2, String> -> @seg:Seg -> @+s:String -> Maybe<&2, Map<&2, String>>
A Param binds the request segment; a Lit must equal it.
def join_rest source · line 240 · raw
@segs:List<&2, String> -> @rev:String -> String
The remaining segments joined with "/": ["a", "b"] is "a/b", [] is "".
def match_path source · line 247 · raw
@pat:List<&2, Seg> -> @segs:List<&2, String> -> @params:Maybe<&2, Map<&2, String>> -> Maybe<&2, Map<&2, String>>
def is_strict source · line 274 · raw
@policy:Slash -> Bool
def slash_ok source · line 285 · raw
@strict:Bool -> @route_slash:Bool -> @path_slash:Bool -> Bool
Under the strict policy a route is only considered when its slash agrees with the request's.
def gate source · line 288 · raw
@ok:Bool -> @m:Maybe<&2, Map<&2, String>> -> Maybe<&2, Map<&2, String>>
def drop_slashes source · line 295 · raw
@+s:String -> String
def with_query source · line 302 · raw
@path:String -> @qs:String -> String
def redirect_target.fin source · line 309 · raw
@pq:0x1efee7e6292e6267db2ca5c7e267456a/air/text.Two -> String
def redirect_target source · line 315 · raw
@+target:String -> String
Where a redirected request goes: the raw target, undecoded, minus its trailing slashes, query kept.
def mark source · line 330 · raw
@m:Map<&2, String> -> @name:String -> Map<&2, String>
def marked source · line 333 · raw
@+m:Map<&2, String> -> @name:String -> Bool
def with_head source · line 336 · raw
@+m:Map<&2, String> -> @has_get:Bool -> Map<&2, String>
def render_allow source · line 345 · raw
@+allowed:Map<&2, String> -> String
The Allow value: the methods offered, plus HEAD when GET is, plus
OPTIONS, sorted and joined with ", ".
def consider.fin source · line 350 · raw
@acc:Pick -> @handler:Handler -> @name:String -> @same:Bool -> @head_get:Bool -> @matched:Maybe<&2, Map<&2, String>> -> Pick
Folds one path-matching route into the pick. same is an exact method
match, head_get a GET route seen by a HEAD request.
def star_match source · line 369 · raw
@star:Bool -> @matched:Maybe<&2, Map<&2, String>> -> Maybe<&2, Map<&2, String>>
OPTIONS * asks about the server, not a path: every route counts as
matching and none is run.
def consider.route source · line 381 · raw
@acc:Pick -> @route:Route -> @+ask:Ask -> Pick
def consider source · line 395 · raw
@acc:Pick -> @route:Route -> @+ask:Ask -> Pick
def select source · line 404 · raw
@routes:List<&1, Route> -> @acc:Pick -> @+ask:Ask -> Pick
def pick source · line 412 · raw
@+policy:Slash -> @routes:List<&1, Route> -> @+req:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Request -> Pick
Walks the routes for the request under the slash policy.
def miss.pick source · line 419 · raw
@+allowed:Map<&2, String> -> @options:Bool -> @none:Bool -> 0x1efee7e6292e6267db2ca5c7e267456a/air/http.Response
A path no route knows is 404. One that routes know under other methods
is 405 with Allow, or 204 with Allow when the request is OPTIONS.
def miss source · line 428 · raw
@+allowed:Map<&2, String> -> @options:Bool -> 0x1efee7e6292e6267db2ca5c7e267456a/air/http.Response
def run source · line 431 · raw
@pick:Pick -> @+req:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Request -> IO(0x1efee7e6292e6267db2ca5c7e267456a/air/http.Response)
def describe.miss source · line 440 · raw
@+allowed:Map<&2, String> -> @none:Bool -> String
def describe source · line 448 · raw
@pick:Pick -> String
What a pick would do, in a word; for laws. A miss lists its Allow.
def dispatch.go source · line 457 · raw
@+policy:Slash -> @routes:List<&1, Route> -> @+req:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Request -> @redirect:Bool -> IO(0x1efee7e6292e6267db2ca5c7e267456a/air/http.Response)
def is_redirect source · line 465 · raw
@policy:Slash -> Bool
def dispatch_with source · line 478 · raw
@+policy:Slash -> @routes:List<&1, Route> -> @+req:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Request -> IO(0x1efee7e6292e6267db2ca5c7e267456a/air/http.Response)
Runs the first route whose method and path match the request under the
slash policy. HEAD runs the GET route when no HEAD route matches. A path
that matches only under other methods answers 405 with Allow, or 204
with Allow to OPTIONS; no match answers 404.
def dispatch source · line 482 · raw
@routes:List<&1, Route> -> @+req:0x1efee7e6292e6267db2ca5c7e267456a/air/http.Request -> IO(0x1efee7e6292e6267db2ca5c7e267456a/air/http.Response)
dispatch_with under the default policy: a trailing slash is ignored.