lib.bend source
lib.bend on the hub · documented module
# Url — a small Base-only URL parser and builder for Bend 2.0.2.## Url v1 models four textual fields: scheme, host, path, and query. Parsing is# deliberately narrower than RFC 3986: only absolute http:// and https:// URLs# are accepted; the authority must contain a non-empty host and no userinfo.# Paths and queries are retained as text, without percent decoding.import Basetype Url is Data: Url{scheme: String, host: String, path: String, query: String}# Construct a URL value without validation. Use Url.parse for the v1 grammar.def Url.make(scheme: String, host: String, path: String, query: String) -> Url: Url{scheme, host, path, query}# Parse a slash-delimited path tail.def Url.parse.path( scheme: String, host: String, query: String, +tail: List<&2, String>) -> Maybe<&2, Url>: match tail: case Nil{}: Some{Url{scheme, host, "", query}} case +first <> +rest: Some{Url{ scheme, host, String.append("/", String.join(first <> rest, "/")), query }}# Reject empty hosts and the userinfo marker before building a URL.def Url.parse.authority.host( scheme: String, query: String, host: String, +tail: List<&2, String>) -> Maybe<&2, Url>: match host: case SNil{}: None{} case SCon{+h, +t}: Bool.pick( Maybe<&2, Url>, String.contains(SCon{h, t}, "@"), None{}, Url.parse.path(scheme, SCon{h, t}, query, tail) )# Parse the authority and path after query splitting. The first slash-delimited# component is the host; the remaining components are rejoined verbatim.def Url.parse.authority( scheme: String, query: String, +parts: List<&2, String>) -> Maybe<&2, Url>: match parts: case Nil{}: None{} case +host <> +tail: Url.parse.authority.host(scheme, query, host, tail)# A query is optional but, when present, must occur exactly once. Fragment# syntax is outside this subset and is rejected by parse.scheme.def Url.parse.query( scheme: String, +parts: List<&2, String>) -> Maybe<&2, Url>: match parts: case Nil{}: None{} case +before <> Nil{}: Url.parse.authority(scheme, "", String.split(before, '/')) case +before <> +query <> Nil{}: Url.parse.authority(scheme, query, String.split(before, '/')) case _ <> _ <> _: None{}def Url.parse.after_scheme( scheme: String, +rest: String) -> Maybe<&2, Url>: Bool.pick( Maybe<&2, Url>, String.contains(rest, "#"), None{}, Url.parse.query(scheme, String.split(rest, '?')) )# Match the fixed lowercase "http://" or "https://" prefix without# duplicating the input string in a Boolean branch.def Url.parse.https.t8(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 47), Url.parse.after_scheme("https", t), None{})def Url.parse.https.t7(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 47), Url.parse.https.t8(t), None{})def Url.parse.https.t6(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 58), Url.parse.https.t7(t), None{})def Url.parse.https.t5(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 115), Url.parse.https.t6(t), None{})def Url.parse.http.t6(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 47), Url.parse.after_scheme("http", t), None{})def Url.parse.http.t5(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 47), Url.parse.http.t6(t), None{})def Url.parse.http.t4(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 58), Url.parse.http.t5(t), Url.parse.https.t6(t))def Url.parse.http.t3(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 112), Url.parse.http.t4(t), None{})def Url.parse.http.t2(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 116), Url.parse.http.t3(t), None{})def Url.parse.http.t(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 116), Url.parse.http.t2(t), None{})# Parse one complete absolute HTTP(S) URL. Scheme matching is lowercase and# exact. No whitespace, fragment, percent decoding, or DNS validation is# imposed beyond the no-userinfo host rule.def Url.parse(s: String) -> Maybe<&2, Url>: match s: case SNil{}: None{} case SCon{Chr{+x}, +t}: Bool.pick(Maybe<&2, Url>, U32.is_eq(x, 104), Url.parse.http.t(t), None{})# Render the four fields in the v1 wire form. An empty query omits '?'.def Url.to_string.query(+head: String, query: String) -> String: match query: case SNil{}: head case SCon{h, t}: String.append(head, String.append("?", SCon{h, t}))def Url.to_string(u: Url) -> String: match u: case Url{scheme, host, path, query}: +head = String.append( String.append(String.append(scheme, "://"), host), path) Url.to_string.query(head, query)def Url.build(u: Url) -> String: Url.to_string(u)def Url.show(u: Url) -> String: Url.to_string(u)# Join one non-empty relative path segment to an existing URL path. A segment# beginning with '/' is not stripped; callers should pass "users", not "/users".def Url.join_path.finish( +acc: String, +segment: String, last_slash: Bool) -> String: match last_slash: case True{}: String.append(String.reverse(acc), segment) case False{}: String.append(String.reverse(acc), String.append("/", segment))def Url.join_path.scan( path: String, +segment: String, +acc: String, last_slash: Bool) -> String: match path: case SNil{}: Url.join_path.finish(acc, segment, last_slash) case SCon{+h, +t}: Bool.pick( String, Char.is_eq(h, '/'), Url.join_path.scan(t, segment, SCon{h, acc}, True{}), Url.join_path.scan(t, segment, SCon{h, acc}, False{}) )def Url.join_path.path(path: String, +segment: String) -> String: Url.join_path.scan(path, segment, SNil{}, False{})def Url.join_path.segment( scheme: String, host: String, path: String, query: String, +segment: String) -> Url: match segment: case SNil{}: Url{scheme, host, path, query} case SCon{+h, +t}: Url{ scheme, host, Url.join_path.path(path, SCon{h, t}), query }def Url.join_path( u: Url, +segment: String) -> Url: match u: case Url{scheme, host, path, query}: Url.join_path.segment(scheme, host, path, query, segment)