MessagePack¶
A MessagePack decoder, written in Sushi: decode, map_get, and show.
Decode-only — there is no encoder.
Import¶
use <encoding/msgpack>
fn main() i32:
return Result.Ok(0)
Overview¶
encoding/msgpack is a Sushi-source standard-library module: it ships as bundled
.sushi source and is merged as a compilation unit when you import it. It is the first
source module with concrete (non-generic) public functions.
decode reads one whole buffer as exactly one MessagePack value and returns a
MsgValue tree. Decode errors are values, not exits.
Types¶
Both are public: a consumer names them, so they are API. The module's own MpCursor is
not, which is what keeps it changeable.
public enum MsgValue:
Nil()
Bool(bool)
Int(i64) # every signed int, and every uint that fits i64
UInt(u64) # only uint64 values above the i64 maximum
Float(f64) # float32 widens to f64
Str(string)
Bin(u8[])
Arr(MsgValue[])
Map(MsgValue[], MsgValue[]) # parallel keys/values, wire order kept
public enum MpError:
Truncated(i32) # byte offset where the input ran out
Unsupported(u8) # ext, fixext, timestamp, or the never-used 0xc1 tag
BadUtf8(i32) # offset just past the offending str payload
Trailing(i32) # bytes remain after the root value
A map is two parallel arrays, not a hash table: MessagePack keys are not limited to strings, and the wire order stays visible and deterministic.
Functions¶
decode(u8[] buf) -> MsgValue | MpError¶
Decode the whole buffer as one value. Trailing bytes after the root value give
MpError.Trailing.
use <encoding/msgpack>
fn show_or_err(u8[] buf) string:
match decode(buf):
Result.Ok(v) ->
return Result.Ok(show(v)??)
Result.Err(_) ->
return Result.Ok("decode error")
fn main() i32:
let u8[] buf = from([0x82, 0xa1, 0x61, 0x01, 0xa1, 0x62, 0x91, 0x02])
println(show_or_err(buf).realise("error")) # {"a":1,"b":[2]}
return Result.Ok(0)
map_get(MsgValue m, string key) -> Maybe@(MsgValue)¶
Scan a Map in wire order for a string key. The found value comes back as a clone, so
the tree stays intact. A missing key, a non-string key match, or a non-map argument gives
Maybe.None.
use <encoding/msgpack>
fn lookup(MsgValue m, string key) string:
let Maybe@(MsgValue) found = map_get(m, key)??
match found:
Maybe.Some(v) ->
return Result.Ok(show(v)??)
Maybe.None() ->
return Result.Ok("missing")
fn main() i32:
let u8[] buf = from([0x81, 0xa1, 0x6b, 0x2a])
match decode(buf):
Result.Ok(m) ->
println(lookup(m, "k").realise("error")) # 42
Result.Err(_) ->
println("decode error")
return Result.Ok(0)
show(MsgValue v) -> string¶
Render a value on one line, deterministically:
| value | rendering |
|---|---|
Nil |
nil |
Bool |
true / false |
Int(42) |
42 |
UInt(v) |
18446744073709551615u (u suffix) |
Float(x) |
f64(4614253070214989087) — the raw IEEE-754 bits of the f64 |
Str("s") |
"s" — quoted, NOT escaped |
Bin |
bin(1,2,3) |
Arr |
[1,[2],nil] |
Map |
{"k":42,"other":[1]} |
Floats render through .to_bits() so the output never depends on float formatting.
Error handling¶
use <encoding/msgpack>
fn classify(u8[] buf) string:
match decode(buf):
Result.Ok(v) ->
return Result.Ok(show(v)??)
Result.Err(e) ->
match e:
MpError.Truncated(off) ->
return Result.Ok("input ended at byte {off}")
MpError.Unsupported(t) ->
return Result.Ok("unsupported tag {t}")
MpError.BadUtf8(off) ->
return Result.Ok("invalid UTF-8 before byte {off}")
MpError.Trailing(p) ->
return Result.Ok("trailing bytes after offset {p}")
fn main() i32:
let u8[] truncated = from([0xa5, 0x68])
println(classify(truncated).realise("error")) # input ended at byte 2
return Result.Ok(0)
Limitations¶
- Decode-only. No encoder, no streaming entry point.
ext,fixext, and timestamp tags giveMpError.Unsupportedwith the tag byte.showdoes not escape string contents.- The decoder targets buffers below 2 GiB. A length prefix is checked against what is
left in the buffer as it is read, so a length the input cannot supply -- a hostile
0xffffffffincluded -- isTruncatedat the header rather than part way through the payload.