339 lines
7.5 KiB
V
339 lines
7.5 KiB
V
module cbor
|
|
|
|
// Value is the dynamic representation of any CBOR data item. Use it when
|
|
// the schema isn't known at compile time, when you need to inspect tags,
|
|
// or when keys aren't strings:
|
|
//
|
|
// v := cbor.decode[cbor.Value](bytes)!
|
|
// match v {
|
|
// cbor.Text { println(v.value) }
|
|
// else { ... }
|
|
// }
|
|
//
|
|
// For known schemas prefer `decode[YourStruct]` — it's faster and avoids
|
|
// the heap allocations of building a Value tree.
|
|
pub type Value = Array
|
|
| Bool
|
|
| Bytes
|
|
| FloatNum
|
|
| IntNum
|
|
| Map
|
|
| Null
|
|
| Simple
|
|
| Tag
|
|
| Text
|
|
| Undefined
|
|
|
|
// IntNum holds the full unsigned/negative CBOR integer range. CBOR allows
|
|
// values from -(2^64) to 2^64-1, which exceeds either i64 or u64 alone, so
|
|
// the sign bit is split out and the magnitude carried as u64.
|
|
//
|
|
// for unsigned: negative=false, magnitude=value
|
|
// for negative: negative=true, magnitude=encoded_argument
|
|
// actual integer = -1 - i64(magnitude) (when it fits i64)
|
|
pub struct IntNum {
|
|
pub:
|
|
negative bool
|
|
magnitude u64
|
|
}
|
|
|
|
// Bytes is a CBOR byte string (major type 2).
|
|
pub struct Bytes {
|
|
pub mut:
|
|
data []u8
|
|
}
|
|
|
|
// Text is a CBOR text string (major type 3, valid UTF-8).
|
|
pub struct Text {
|
|
pub:
|
|
value string
|
|
}
|
|
|
|
// Array holds the elements of a CBOR array (major type 4).
|
|
pub struct Array {
|
|
pub mut:
|
|
elements []Value
|
|
}
|
|
|
|
// MapPair represents one key/value entry in a CBOR map. CBOR allows any
|
|
// data item as a key, so we keep an ordered list of pairs rather than
|
|
// using V's `map[K]V`.
|
|
pub struct MapPair {
|
|
pub:
|
|
key Value
|
|
value Value
|
|
}
|
|
|
|
// Map holds the ordered key/value pairs of a CBOR map (major type 5).
|
|
pub struct Map {
|
|
pub mut:
|
|
pairs []MapPair
|
|
}
|
|
|
|
// Tag wraps a tagged data item (major type 6). The content is stored in
|
|
// a one-element slice rather than as a `&Value` reference: V can box and
|
|
// recurse a sumtype through a slice, while a direct `&Value` field
|
|
// requires manual heap allocation. Use `tag.content()` to access it.
|
|
pub struct Tag {
|
|
pub:
|
|
number u64
|
|
content_box []Value
|
|
}
|
|
|
|
// content returns the Value enclosed by a Tag, or `Null{}` if missing.
|
|
@[inline]
|
|
pub fn (t &Tag) content() Value {
|
|
if t.content_box.len > 0 {
|
|
return t.content_box[0]
|
|
}
|
|
return Null{}
|
|
}
|
|
|
|
// Bool is the wrapped form of CBOR true/false (simple values 20/21).
|
|
pub struct Bool {
|
|
pub:
|
|
value bool
|
|
}
|
|
|
|
// Null is the wrapped form of CBOR null (simple value 22).
|
|
pub struct Null {}
|
|
|
|
// Undefined is the wrapped form of CBOR undefined (simple value 23).
|
|
pub struct Undefined {}
|
|
|
|
// FloatBits records which IEEE 754 width the float was originally encoded
|
|
// at. The encoder honours this when re-emitting a Value, so round-tripping
|
|
// preserves the original byte width.
|
|
pub enum FloatBits as u8 {
|
|
@none = 0
|
|
half = 16
|
|
single = 32
|
|
double = 64
|
|
}
|
|
|
|
// FloatNum is a CBOR floating-point value (major type 7, additional info
|
|
// 25/26/27). `bits` records the wire width for fidelity on re-encoding;
|
|
// the default `.@none` lets the encoder pick the shortest IEEE 754 width
|
|
// that preserves the value (RFC 8949 §4.2.2 preferred serialisation), so
|
|
// hand-built FloatNum literals don't accidentally lock in 8-byte output.
|
|
pub struct FloatNum {
|
|
pub:
|
|
value f64
|
|
bits FloatBits = .@none
|
|
}
|
|
|
|
// Simple is the catch-all for major type 7 simple values 0..255 not
|
|
// otherwise covered by Bool/Null/Undefined.
|
|
pub struct Simple {
|
|
pub:
|
|
value u8
|
|
}
|
|
|
|
// new_uint wraps a u64 in a Value (unsigned-int variant).
|
|
@[inline]
|
|
pub fn new_uint(n u64) Value {
|
|
return IntNum{
|
|
negative: false
|
|
magnitude: n
|
|
}
|
|
}
|
|
|
|
// new_int wraps a signed i64 in a Value, picking unsigned vs negative.
|
|
@[inline]
|
|
pub fn new_int(n i64) Value {
|
|
if n >= 0 {
|
|
return IntNum{
|
|
negative: false
|
|
magnitude: u64(n)
|
|
}
|
|
}
|
|
return IntNum{
|
|
negative: true
|
|
magnitude: u64(-(n + 1))
|
|
}
|
|
}
|
|
|
|
// new_negative wraps the encoded argument of a major-type-1 value, where
|
|
// the actual integer is -1 - magnitude. Useful when magnitude exceeds i64.
|
|
@[inline]
|
|
pub fn new_negative(magnitude u64) Value {
|
|
return IntNum{
|
|
negative: true
|
|
magnitude: magnitude
|
|
}
|
|
}
|
|
|
|
// new_text wraps a string as a CBOR text Value.
|
|
@[inline]
|
|
pub fn new_text(s string) Value {
|
|
return Text{
|
|
value: s
|
|
}
|
|
}
|
|
|
|
// new_bytes wraps a []u8 as a CBOR byte-string Value.
|
|
@[inline]
|
|
pub fn new_bytes(b []u8) Value {
|
|
return Bytes{
|
|
data: b
|
|
}
|
|
}
|
|
|
|
// new_float wraps an f64 as a CBOR FloatNum that re-encodes at full
|
|
// precision unless `f64_to_half` / f32 conversion is lossless.
|
|
@[inline]
|
|
pub fn new_float(v f64) Value {
|
|
return FloatNum{
|
|
value: v
|
|
bits: .@none
|
|
}
|
|
}
|
|
|
|
// new_tag wraps an existing Value with a tag number.
|
|
@[inline]
|
|
pub fn new_tag(number u64, content Value) Value {
|
|
return Tag{
|
|
number: number
|
|
content_box: [content]
|
|
}
|
|
}
|
|
|
|
// is_nil returns true if v is the CBOR `null` value.
|
|
@[inline]
|
|
pub fn (v &Value) is_nil() bool {
|
|
return v is Null
|
|
}
|
|
|
|
// is_undefined returns true if v is the CBOR `undefined` value.
|
|
@[inline]
|
|
pub fn (v &Value) is_undefined() bool {
|
|
return v is Undefined
|
|
}
|
|
|
|
// as_int returns the value as an i64 when it fits, or none otherwise.
|
|
// Returns none for FloatNum, Text, etc.
|
|
//
|
|
// CBOR negative integers represent `-1 - magnitude`, so magnitude
|
|
// `2^63 - 1` maps to i64::min and magnitude `2^63` would map to
|
|
// `-2^63 - 1` — outside i64 range — hence the strict ">" cutoff for
|
|
// negatives. Use `as_uint` plus the `negative` flag to recover the
|
|
// full -2^64..2^64-1 CBOR range.
|
|
pub fn (v &Value) as_int() ?i64 {
|
|
if v is IntNum {
|
|
if v.negative {
|
|
if v.magnitude > u64(max_i64) {
|
|
return none
|
|
}
|
|
return -1 - i64(v.magnitude)
|
|
}
|
|
if v.magnitude > u64(max_i64) {
|
|
return none
|
|
}
|
|
return i64(v.magnitude)
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_uint returns the value as a u64 if it's a non-negative integer, else none.
|
|
pub fn (v &Value) as_uint() ?u64 {
|
|
if v is IntNum {
|
|
if v.negative {
|
|
return none
|
|
}
|
|
return v.magnitude
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_float returns the f64 value, or none if v isn't a FloatNum.
|
|
pub fn (v &Value) as_float() ?f64 {
|
|
if v is FloatNum {
|
|
return v.value
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_bool returns the boolean value, or none if v isn't a Bool.
|
|
pub fn (v &Value) as_bool() ?bool {
|
|
if v is Bool {
|
|
return v.value
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_string returns the text-string value, or none if v isn't Text.
|
|
pub fn (v &Value) as_string() ?string {
|
|
if v is Text {
|
|
return v.value
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_bytes returns the byte-string payload, or none if v isn't Bytes.
|
|
pub fn (v &Value) as_bytes() ?[]u8 {
|
|
if v is Bytes {
|
|
return v.data
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_array returns the elements of an Array, or none.
|
|
pub fn (v &Value) as_array() ?[]Value {
|
|
if v is Array {
|
|
return v.elements
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_map returns the pairs of a Map, or none.
|
|
pub fn (v &Value) as_map() ?[]MapPair {
|
|
if v is Map {
|
|
return v.pairs
|
|
}
|
|
return none
|
|
}
|
|
|
|
// as_tag returns (number, content) of a Tag, or none.
|
|
pub fn (v &Value) as_tag() ?(u64, Value) {
|
|
if v is Tag {
|
|
return v.number, v.content()
|
|
}
|
|
return none
|
|
}
|
|
|
|
// get does a linear lookup of a string-keyed entry in a Map.
|
|
// O(n) — for hot paths decode into a typed struct or `map[string]V`.
|
|
pub fn (v &Value) get(key string) ?Value {
|
|
if v is Map {
|
|
for pair in v.pairs {
|
|
if pair.key is Text {
|
|
if pair.key.value == key {
|
|
return pair.value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return none
|
|
}
|
|
|
|
// at returns the element at `index` of an Array.
|
|
pub fn (v &Value) at(index int) ?Value {
|
|
if v is Array {
|
|
if index >= 0 && index < v.elements.len {
|
|
return v.elements[index]
|
|
}
|
|
}
|
|
return none
|
|
}
|
|
|
|
// len returns the length of an Array, Map, Text, or Bytes value, or 0.
|
|
pub fn (v &Value) len() int {
|
|
match v {
|
|
Array { return v.elements.len }
|
|
Map { return v.pairs.len }
|
|
Text { return v.value.len }
|
|
Bytes { return v.data.len }
|
|
else { return 0 }
|
|
}
|
|
}
|