http/query.bend source
http/query.bend on the hub · documented module
import Baseimport ./text.bend as Timport ../utf8/utf8.bend as U# a query string's pairs, in order and with repeats kept: ?tag=a&tag=b is# two pairs, which a map would lose (application/x-www-form-urlencoded)type Param is Data: Param{key: String, value: String}def hex(+x: U32) -> Maybe<&2, U32>: Bool.pick(Maybe<&2, U32>, Bool.and(U32.is_ge(x, 48), U32.is_le(x, 57)), Some{(x - 48 : U32)}, Bool.pick(Maybe<&2, U32>, Bool.and(U32.is_ge(x, 97), U32.is_le(x, 102)), Some{(x - 87 : U32)}, Bool.pick(Maybe<&2, U32>, Bool.and(U32.is_ge(x, 65), U32.is_le(x, 70)), Some{(x - 55 : U32)}, None{})))def pct.byte(a: Maybe<&2, U32>, b: Maybe<&2, U32>) -> Maybe<&2, U32>: match a b: case Some{+x} Some{+y}: Some{(x * 16 + y : U32)} case _ _: None{}def pct.push(m: Maybe<&2, U32>, +x: U32, +y: U32, out: List<&2, U32>) -> List<&2, U32>: match m: case Some{b}: Con{b, out} case None{}: Con{y, Con{x, Con{37, out}}}# the bytes of a component: %XX is a byte, + a space; a % not followed by# two hex digits stays as it isdef bytes.go(s: String, out: List<&2, U32>) -> List<&2, U32>: match s: case SNil{}: List.reverse(&2, U32, out) case SCon{Chr{37}, SCon{Chr{+x}, SCon{Chr{+y}, t}}}: bytes.go(t, pct.push(pct.byte(hex(x), hex(y)), x, y, out)) case SCon{Chr{43}, t}: bytes.go(t, Con{32, out}) case SCon{Chr{x}, t}: bytes.go(t, U.enc.push(x, out))def text.of(t: U.Text) -> String: match t: case U.TOk{s}: s case U.TBad{_, before}: String.reverse(before)# a component decoded: bytes that are not UTF-8 end it where they startdef decode(s: String) -> String: text.of(U.decode(bytes.go(s, [])))def pair(c: T.Cut) -> Param: match c: case T.Cut{k, Some{v}}: Param{decode(k), decode(v)} case T.Cut{k, None{}}: Param{decode(k), ""}def add(p: String, acc: List<&2, Param>) -> List<&2, Param>: match p: case SNil{}: acc case SCon{c, t}: Con{pair(T.cut(SCon{c, t}, 61)), acc}# c is the query cut at its first &: the pair before it, and what followsdef parse.go(fuel: Nat, c: T.Cut, acc: List<&2, Param>) -> List<&2, Param>: match fuel c: case 1n+f T.Cut{p, Some{rest}}: parse.go(f, T.cut(rest, 38), add(p, acc)) case _ T.Cut{p, _}: List.reverse(&2, Param, add(p, acc))# the pairs of a query string, without its ?def parse(+q: String) -> List<&2, Param>: parse.go(String.length(q), T.cut(q, 38), [])