~/bend-docscommunity

http_json/http_json.bend source

http_json/http_json.bend on the hub · documented module

import Baseimport ../json/json.bend as Jimport ../utf8/utf8.bend as Uimport ../http/status.bend as Simport ../http/headers.bend as Himport ../http/request.bend as Reqimport ../http/response.bend as Res# JSON bodies for http: decode a request's body into your type, and answer# with a JSON value# why a body did not decodetype DecodeError is Data:  UnsupportedContentType{}  BodyTooLarge{}  InvalidJson{error: J.Error}  InvalidValue{error: J.Access}# the largest body decode reads: 1 MiBdef limit() -> U32:  1048576def lower.go(up: Bool, +x: U32) -> U32:  match up:    case True{}:      (x + 32 : U32)    case False{}:      xdef lower(+x: U32) -> U32:  lower.go(Bool.and(U32.is_ge(x, 65), U32.is_le(x, 90)), x)# the media type, lowercased, spaces dropped, reversed: it ends at ';'def media.go(s: String, acc: String) -> String:  match s:    case SNil{}:      acc    case SCon{';', _}:      acc    case SCon{' ', t}:      media.go(t, acc)    case SCon{'\t', t}:      media.go(t, acc)    case SCon{Chr{+x}, t}:      media.go(t, SCon{Chr{lower(x)}, acc})def starts(s: String, p: String) -> Bool:  match s p:    case _ SNil{}:      True{}    case SCon{x, st} SCon{y, pt}:      Bool.and(Char.is_eq(x, y), starts(st, pt))    case SNil{} SCon{_, _}:      False{}def same(a: String, b: String) -> Bool:  match a b:    case SNil{} SNil{}:      True{}    case SCon{x, at} SCon{y, bt}:      Bool.and(Char.is_eq(x, y), same(at, bt))    case _ _:      False{}def is_json.of(+rev: String) -> Bool:  Bool.or(same(rev, "nosj/noitacilppa"), starts(rev, "nosj+"))# application/json, or a type with the +json suffix (RFC 6839 §3.1)def is_json(m: Maybe<&2, String>) -> Bool:  match m:    case Some{s}:      is_json.of(media.go(s, ""))    case None{}:      False{}# a body and its length, counted in one readtype Sized is Data:  Sized{n: U32, body: List<&2, U32>}def measure(bs: List<&2, U32>, +n: U32, acc: List<&2, U32>) -> Sized:  match bs:    case Nil{}:      Sized{n, List.reverse(&2, U32, acc)}    case Con{b, t}:      measure(t, (n + 1 : U32), Con{b, acc})def decode.value(-A: Data, r: Result<&2, &2, J.Access, A>) -> Result<&2, &2, DecodeError, A>:  match r:    case Done{a}:      Done{a}    case Fail{e}:      Fail{InvalidValue{e}}def decode.json(-A: Data, f: J.Json -> Result<&2, &2, J.Access, A>, r: Result<&2, &2, J.Error, J.Json>) -> Result<&2, &2, DecodeError, A>:  match r:    case Done{j}:      decode.value(A, f(j))    case Fail{e}:      Fail{InvalidJson{e}}def decode.fits(-A: Data, f: J.Json -> Result<&2, &2, J.Access, A>, ok: Bool, bs: List<&2, U32>) -> Result<&2, &2, DecodeError, A>:  match ok:    case True{}:      decode.json(A, f, J.parse_bytes(bs))    case False{}:      Fail{BodyTooLarge{}}def decode.sized(-A: Data, f: J.Json -> Result<&2, &2, J.Access, A>, s: Sized) -> Result<&2, &2, DecodeError, A>:  match s:    case Sized{n, bs}:      decode.fits(A, f, U32.is_le(n, limit()), bs)def decode.typed(-A: Data, f: J.Json -> Result<&2, &2, J.Access, A>, ok: Bool, bs: List<&2, U32>) -> Result<&2, &2, DecodeError, A>:  match ok:    case True{}:      decode.sized(A, f, measure(bs, 0, []))    case False{}:      Fail{UnsupportedContentType{}}# the body as your type: f reads the parsed JSON, e.g. with J.string_atdef decode(-A: Data, f: J.Json -> Result<&2, &2, J.Access, A>, r: Req.Request) -> Result<&2, &2, DecodeError, A>:  match r:    case Req.Request{_, _, hs, body}:      decode.typed(A, f, is_json(H.get(hs, "content-type")), body)# a JSON answerdef response(s: S.Status, j: J.Json) -> Res.Response:  Res.typed(s, "application/json", U.encode(J.encode(j)))def error_body(msg: String) -> J.Json:  J.JObj{[J.Field{"error", J.JStr{msg}}]}# the answer to a body that did not decode: 415, 413 or 400, with# {"error": why}def error(e: DecodeError) -> Res.Response:  match e:    case UnsupportedContentType{}:      response(S.unsupported_media_type(), error_body("content-type must be application/json"))    case BodyTooLarge{}:      response(S.content_too_large(), error_body("body too large"))    case InvalidJson{x}:      response(S.bad_request(), error_body("invalid JSON: " ++ J.message(x)))    case InvalidValue{a}:      response(S.bad_request(), error_body(J.message_access(a)))