~/bend-docscommunity

input.bend source

input.bend on the hub · documented module

# Checked typed inputs; bodies are consumed once, without caching or decompression.import Baseimport bend-kit-http@0.32.0.0/http.bend as Httpimport bend-kit-router@0.2.0.0/target.bend as Targetimport bend-kit-json@0.5.1.0/json.bend as Jsonimport 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytesimport bend-kit-url@0.4.1.0/url.bend as Urlimport ./response.bend as Response# Expected HTTP status, not an exception; the dispatch executor maps it once.def checked(~I: Type, value: Maybe<&1, I>) -> Result<&2, &1, U32, I>:  match value:    case None{}: Fail{400}    case Some{input}: Done{input}def decimal.step(ok: Bool, +acc: U32, digit: U32, next: U32 -> Maybe<&2, U32>) -> Maybe<&2, U32>:  match ok:    case False{}: None{}    case True{}: next((acc * 10 + digit : U32))def decimal.go(text: String, +acc: U32) -> Maybe<&2, U32>:  match text:    case SNil{}: Some{acc}    case SCon{Chr{+c}, rest}:      +digit = (c - 48 : U32)      decimal.step(U32.is_ge(c, 48) && U32.is_le(c, 57) && (U32.is_lt(acc, 429496729) || (U32.is_eq(acc, 429496729) && U32.is_le(digit, 5))), acc, digit, n => decimal.go(rest, n))# Nonempty ASCII decimal only; arithmetic happens only after the overflow guard.def u32(text: String) -> Maybe<&2, U32>:  match text:    case SNil{}: None{}    case SCon{c, rest}: decimal.go(SCon{c, rest}, 0)def scalar.convert(~I: Data, value: Maybe<&2, I>) -> Result<&2, &1, U32, I>:  match value:    case None{}: Fail{400}    case Some{input}: Done{input}def path.value(~I: Data, ~convert: String -> Maybe<&2, I>, pair: Map<&2, String> & String) -> Result<&2, &1, U32, I>:  (params, value) = pair  scalar.convert(~I, convert(value))def path.present(~I: Data, ~convert: String -> Maybe<&2, I>, present: Bool, params: Map<&2, String>, key: String) -> Result<&2, &1, U32, I>:  match present:    case False{}: Fail{400}    case True{}:      path.value(~I, ~convert, Map.get(String, "", params, key))def path.found(~I: Data, ~convert: String -> Maybe<&2, I>, key: String, pair: Map<&2, String> & Bool) -> Result<&2, &1, U32, I>:  (params, present) = pair  path.present(~I, ~convert, present, params, key)def path(~I: Data, ~convert: String -> Maybe<&2, I>, params: Map<&2, String>, +key: String) -> Result<&2, &1, U32, I>:  path.found(~I, ~convert, key, Map.has(&2, String, params, key))def query.keep(same: Bool, value: String, rest: List<&2, String>) -> List<&2, String>:  match same:    case True{}: value <> rest    case False{}: restdef query.values(xs: List<&2, Target.Query>, +key: String) -> List<&2, String>:  match xs:    case Nil{}: Nil{}    case Target.Query{+name, value} <> rest:      query.keep(String.eq(name, key), value, query.values(rest, key))def scalar.values(~I: Data, ~convert: String -> Maybe<&2, I>, values: List<&2, String>) -> Result<&2, &1, U32, I>:  match values:    case value <> Nil{}: scalar.convert(~I, convert(value))    case _: Fail{400}def query.scalar(~I: Data, ~convert: String -> Maybe<&2, I>, query: List<&2, Target.Query>, key: String) -> Result<&2, &1, U32, I>:  scalar.values(~I, ~convert, query.values(query, key))def list.values(~I: Data, ~convert: String -> Maybe<&2, I>, values: List<&2, String>) -> Result<&2, &1, U32, List<&2, I>>:  match values:    case Nil{}: Done{Nil{}}    case value <> rest:      do Result<&2, &1, U32, List<&2, I>>:        head : I <- scalar.convert(~I, convert(value))        tail : List<&2, I> <- list.values(~I, ~convert, rest)        return head <> taildef query.list(~I: Data, ~convert: String -> Maybe<&2, I>, query: List<&2, Target.Query>, key: String) -> Result<&2, &1, U32, List<&2, I>>:  list.values(~I, ~convert, query.values(query, key))# Reuse Target's RFC 3629 state machine directly over the owned packed array.def utf8.step(pair: Array<U32> & U32, state: Target.Utf) -> Array<U32> & Maybe<&2, Target.Utf>:  (bytes, byte) = pair  Target.Utf{+need, lo, hi, cp, out} = state  (bytes, Target.utf.step(U32.is_eq(need, 0), byte, need, lo, hi, cp, out))def utf8.go(fuel: Nat, pair: Array<U32> & Maybe<&2, Target.Utf>, +index: U32) -> Array<U32> & Maybe<&2, String>:  match fuel:    case 0n:      (bytes, state) = pair      (bytes, Target.utf.finish(state))    case 1n+rest:      (bytes, state) = pair      match state:        case None{}: (bytes, None{})        case Some{state}: utf8.go(rest, utf8.step(Bytes.peek(bytes, index), state), (index + 1 : U32))def utf8.borrowed(len: U32, pair: Array<U32> & Maybe<&2, String>) -> Http.Body & Maybe<&2, String>:  (buffer, text) = pair  (Bytes.Bytes{len, buffer}, text)def utf8.borrow(bytes: Http.Body) -> Http.Body & Maybe<&2, String>:  Bytes.Bytes{+len, buffer} = bytes  utf8.borrowed(len, utf8.go(U32.to_nat(len), (buffer, Some{Target.Utf{0, 128, 191, 0, ""}}), 0))def utf8.text(pair: Http.Body & Maybe<&2, String>) -> Maybe<&2, String>:  (bytes, text) = pair  textdef utf8(bytes: Http.Body) -> Maybe<&2, String>:  utf8.text(utf8.borrow(bytes))# Split only unquoted semicolons, retaining quoted parameter syntax for checks.def media.part(split: Bool, +c: U32, +quoted: Bool, +escaped: Bool, acc: String, parts: List<&2, String>) -> Bool & Bool & String & List<&2, String>:  match split:    case True{}: (False{}, False{}, "", String.reverse(acc) <> parts)    case False{}:      (Bool.pick(Bool, U32.is_eq(c, 34) && Bool.not(escaped), Bool.not(quoted), quoted), quoted && U32.is_eq(c, 92) && Bool.not(escaped), SCon{Chr{c}, acc}, parts)def media.end(invalid: Bool, acc: String, parts: List<&2, String>) -> List<&2, String>:  match invalid:    case True{}: ["invalid"]    case False{}: List.reverse(&2, String, String.reverse(acc) <> parts)def media.parts(text: String, state: Bool & Bool & String & List<&2, String>) -> List<&2, String>:  match text:    case SNil{}:      (quoted, escaped, acc, parts) = state      media.end(quoted || escaped, acc, parts)    case SCon{Chr{+c}, rest}:      (+quoted, escaped, acc, parts) = state      media.parts(rest, media.part(U32.is_eq(c, 59) && Bool.not(quoted), c, quoted, escaped, acc, parts))# Compare the decoded quoted-string charset without allocating a second value.def media.charset.quoted(text: String, expected: String, escaped: Bool) -> Bool:  match text:    case SNil{}: False{}    case SCon{Chr{+c}, rest}:      match c:        case 92:          match expected:            case SNil{}: False{}            case SCon{Chr{want}, next}:              match escaped:                case True{}: False{}                case False{}: media.charset.quoted(rest, SCon{Chr{want}, next}, True{})        case _:          match expected:            case SNil{}: U32.is_eq(c, 34) && Bool.not(escaped) && String.eq(rest, "")            case SCon{Chr{want}, next}: U32.is_eq(c, want) && media.charset.quoted(rest, next, False{})def media.charset.start(quoted: Bool, c: U32, rest: String) -> Bool:  match quoted:    case True{}: media.charset.quoted(rest, "utf-8", False{})    case False{}: String.eq(SCon{Chr{c}, rest}, "utf-8")def media.charset.value(value: String) -> Bool:  match value:    case SNil{}: False{}    case SCon{Chr{+c}, rest}: media.charset.start(U32.is_eq(c, 34), c, rest)def media.parameter(pair: String & String) -> Bool:  (key, value) = pair  +name = String.to_lower(Http.trim(key))  +charset = String.to_lower(Http.trim(value))  Bool.not(String.eq(name, "charset")) || media.charset.value(charset)def media.charset(parts: List<&2, String>) -> Bool:  match parts:    case Nil{}: True{}    case part <> rest: media.parameter(Url.take_to(part, 61)) && media.charset(rest)def media.type(json: Bool, +name: String) -> Bool:  match json:    case True{}:      String.eq(name, "application/json") || (String.starts_with(name, "application/") && String.ends_with(name, "+json") && Nat.is_gt(String.length(name), 17n) && Response.name(String.drop(name, 12n)))    case False{}:      String.starts_with(name, "text/") && Response.name(String.drop(name, 5n))def media.content(json: Bool, parts: List<&2, String>) -> Bool:  match parts:    case Nil{}: False{}    case name <> rest: media.type(json, String.to_lower(Http.trim(name))) && media.charset(rest)def media.encoding(values: List<&2, String>) -> Bool:  match values:    case Nil{}: True{}    case value <> rest: String.eq(value, "identity") && media.encoding(rest)def media.headers(json: Bool, types: List<&2, String>, encodings: List<&2, String>) -> Bool:  match types:    case value <> Nil{}: media.content(json, media.parts(value, (False{}, False{}, "", Nil{}))) && media.encoding(Http.ce.split(encodings))    case _: False{}def media(json: Bool, +headers: Map<&2, List<&2, String>>) -> Bool:  media.headers(json, Http.fields(headers, "content-type"), Http.fields(headers, "content-encoding"))def raw(body: Http.Body) -> Result<&2, &1, U32, Http.Body>:  Done{body}def text.accepted(accepted: Bool, body: Http.Body) -> Result<&2, &1, U32, String>:  match accepted:    case False{}: Fail{415}    case True{}: scalar.convert(~String, utf8(body))def text(headers: Map<&2, List<&2, String>>, body: Http.Body) -> Result<&2, &1, U32, String>:  text.accepted(media(False{}, headers), body)# An explicit stack avoids mutually recursive tree walkers. Each decoded object# has its own key set; escaped and literal spellings compare as the same text.type Parent is Type:  Object{key: Bytes.Bytes, rest: List<&1, Bytes.Bytes & Json.Val>, acc: List<&1, Bytes.Bytes & Json.Val>, seen: Set()}  Array{rest: List<&1, Json.Val>, acc: List<&1, Json.Val>}type Walk is Type:  Value{value: Json.Val, parents: List<&1, Parent>}  Fields{fields: List<&1, Bytes.Bytes & Json.Val>, acc: List<&1, Bytes.Bytes & Json.Val>, seen: Set(), parents: List<&1, Parent>}  Items{values: List<&1, Json.Val>, acc: List<&1, Json.Val>, parents: List<&1, Parent>}  Returned{value: Json.Val, parents: List<&1, Parent>}def unique.field(repeated: Bool, key: Bytes.Bytes, value: Json.Val, rest: List<&1, Bytes.Bytes & Json.Val>, acc: List<&1, Bytes.Bytes & Json.Val>, seen: Set(), parents: List<&1, Parent>) -> Maybe<&1, Walk>:  match repeated:    case True{}: None{}    case False{}: Some{Value{value, Object{key, rest, acc, seen} <> parents}}def unique.seen(pair: Set() & Bool, name: String, key: Bytes.Bytes, value: Json.Val, rest: List<&1, Bytes.Bytes & Json.Val>, acc: List<&1, Bytes.Bytes & Json.Val>, parents: List<&1, Parent>) -> Maybe<&1, Walk>:  (seen, repeated) = pair  unique.field(repeated, key, value, rest, acc, Set.add(seen, name), parents)def unique.key(text: Maybe<&2, String>, key: Bytes.Bytes, value: Json.Val, rest: List<&1, Bytes.Bytes & Json.Val>, acc: List<&1, Bytes.Bytes & Json.Val>, seen: Set(), parents: List<&1, Parent>) -> Maybe<&1, Walk>:  match text:    case None{}: None{}    case Some{+name}:      unique.seen(Set.has(seen, name), name, key, value, rest, acc, parents)def unique.borrowed(pair: Http.Body & Maybe<&2, String>, value: Json.Val, rest: List<&1, Bytes.Bytes & Json.Val>, acc: List<&1, Bytes.Bytes & Json.Val>, seen: Set(), parents: List<&1, Parent>) -> Maybe<&1, Walk>:  (key, text) = pair  unique.key(text, key, value, rest, acc, seen, parents)def unique.step(step: Walk) -> Maybe<&1, Walk>:  match step:    case Value{value, parents}:      match value:        case Json.Obj{fields}: Some{Fields{fields, Nil{}, Set.new(), parents}}        case Json.Arr{values}: Some{Items{values, Nil{}, parents}}        case Json.Null{}: Some{Returned{Json.Null{}, parents}}        case Json.Flag{on}: Some{Returned{Json.Flag{on}, parents}}        case Json.Num{number}: Some{Returned{Json.Num{number}, parents}}        case Json.Str{text}: Some{Returned{Json.Str{text}, parents}}    case Fields{fields, acc, seen, parents}:      match fields:        case Nil{}: Some{Returned{Json.Obj{List.reverse(&1, Bytes.Bytes & Json.Val, acc)}, parents}}        case (key, value) <> rest:          unique.borrowed(utf8.borrow(key), value, rest, acc, seen, parents)    case Items{values, acc, parents}:      match values:        case Nil{}: Some{Returned{Json.Arr{List.reverse(&1, Json.Val, acc)}, parents}}        case value <> rest: Some{Value{value, Array{rest, acc} <> parents}}    case Returned{value, parents}:      match parents:        case Nil{}: Some{Returned{value, Nil{}}}        case Object{key, rest, acc, seen} <> parents: Some{Fields{rest, (key, value) <> acc, seen, parents}}        case Array{rest, acc} <> parents: Some{Items{rest, value <> acc, parents}}def unique(fuel: Nat, step: Maybe<&1, Walk>) -> Maybe<&1, Json.Val>:  match fuel:    case 0n: None{}    case 1n+rest:      match step:        case None{}: None{}        case Some{Returned{value, Nil{}}}: Some{value}        case Some{state}: unique(rest, unique.step(state))def json.parsed(fuel: Nat, value: Maybe<&1, Json.Val>) -> Result<&2, &1, U32, Json.Val>:  match value:    case None{}: Fail{400}    case Some{value}: checked(~Json.Val, unique(fuel, Some{Value{value, Nil{}}}))def json.accepted(accepted: Bool, cap: U32, body: Http.Body) -> Result<&2, &1, U32, Json.Val>:  match accepted:    case False{}: Fail{415}    case True{}:      Bytes.Bytes{+len, buffer} = body      json.parsed(Nat.add(Nat.mul(U32.to_nat(len), 4n), 8n), Json.parse.strict.bytes(Bytes.Bytes{len, buffer}, cap))def json.with_depth(headers: Map<&2, List<&2, String>>, body: Http.Body, cap: U32) -> Result<&2, &1, U32, Json.Val>:  json.accepted(media(True{}, headers), cap, body)def json(headers: Map<&2, List<&2, String>>, body: Http.Body) -> Result<&2, &1, U32, Json.Val>:  json.with_depth(headers, body, 64)# Exact number text remains packed until this explicit checked conversion.def number.u32(value: Json.Val) -> Result<&2, &1, U32, U32>:  match value:    case Json.Num{bytes}: scalar.convert(~U32, u32(Bytes.to_string(bytes)))    case _: Fail{400}