1 import Base 2 import bend-kit-json@0.3.0.0/json.bend as Json 3 import ./JsonAdapter.bend as JsonAdapter 4 import bend-codec-lib@0.2.0.0/bytes.bend as ByteLib 5 import bend-codec-lib@0.2.0.0/hex.bend as HexLib 6 7 # Multi-type stored values. Canonical forms are single-key objects so every 8 # constructor hashes differently: {"t": s}, {"o": doc}, {"b": hex}. Binary 9 # blobs hex-encode through the pinned codec and stay generic binary data, 10 # never format-specific. Kit is named for the Val TYPE only; all JSON work 11 # goes through JsonAdapter. 12 13 # One stored record value: text, structured document, or binary blob. 14 type Value is Data: 15 Text{content: String} 16 Object{object: Json.Val} 17 Blob{bytes: List<&2, U32>} 18 19 # Canonical JSON of a value; the hashed bytes. Distinct keys per 20 # constructor keep different values from sharing an address. 21 def value_to_json(value: Value) -> Json.Val: 22 match value: 23 case Text{content}: 24 JsonAdapter.make_obj(Con{JsonAdapter.make_kv("t", JsonAdapter.make_str(content)), Nil{}}) 25 case Object{document}: 26 JsonAdapter.make_obj(Con{JsonAdapter.make_kv("o", document), Nil{}}) 27 case Blob{byte_list}: 28 JsonAdapter.make_obj(Con{JsonAdapter.make_kv("b", JsonAdapter.make_str(HexLib.Hex.encode(ByteLib.Bytes{byte_list}, HexLib.HexLower{}))), Nil{}}) 29 30 # Canonical text of a value. 31 def value_to_canonical(value: Value) -> String: 32 JsonAdapter.encode_canonical(value_to_json(value)) 33 34 # Reads the "t" field as text. 35 def text_string_or_nothing(text_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, String>: 36 match text_maybe: 37 case None{}: 38 None{} 39 case Some{text_val}: 40 JsonAdapter.as_str(text_val) 41 42 # Unwraps the decoded byte box. 43 def bytes_data(byte_box: ByteLib.Bytes) -> List<&2, U32>: 44 match byte_box: 45 case ByteLib.Bytes{raw_data}: 46 raw_data 47 48 # Reads a hex decode result as a blob; malformed hex reads as None. 49 def blob_or_nothing(decode_result: Result<&2, &2, HexLib.Hex.Error, ByteLib.Bytes>) -> Maybe<&2, Value>: 50 match decode_result: 51 case Fail{decode_error}: 52 None{} 53 case Done{byte_box}: 54 Some{Blob{bytes_data(byte_box)}} 55 56 # Reads the "b" field as a blob. 57 def blob_from_hex(hex_maybe: Maybe<&2, String>) -> Maybe<&2, Value>: 58 match hex_maybe: 59 case None{}: 60 None{} 61 case Some{hex_text}: 62 blob_or_nothing(HexLib.Hex.decode(hex_text)) 63 64 # Reads the hex text behind the "b" field. 65 def hex_text_or_nothing(hex_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, String>: 66 match hex_maybe: 67 case None{}: 68 None{} 69 case Some{hex_val}: 70 JsonAdapter.as_str(hex_val) 71 72 # Falls through from text to object to blob; unknown shapes read as None. 73 def from_object_value(object_maybe: Maybe<&2, Json.Val>, hex_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, Value>: 74 match object_maybe: 75 case None{}: 76 blob_from_hex(hex_text_or_nothing(hex_maybe)) 77 case Some{document}: 78 Some{Object{document}} 79 80 # Falls through from text to the remaining shapes. 81 def from_text_string(text_maybe: Maybe<&2, String>, +document: Json.Val) -> Maybe<&2, Value>: 82 match text_maybe: 83 case None{}: 84 from_object_value(JsonAdapter.get_field(document, "o"), JsonAdapter.get_field(document, "b")) 85 case Some{content}: 86 Some{Text{content}} 87 88 # Parses canonical value JSON; unknown shapes read as None. 89 def value_from_json(+document: Json.Val) -> Maybe<&2, Value>: 90 from_text_string(text_string_or_nothing(JsonAdapter.get_field(document, "t")), document)