v / vlib / encoding / cbor / value.v
338 lines · 302 sloc · 7.54 KB · 468855eef1db0ff73c62be2d1bf176ffa0e1478e
Raw
1module cbor
2
3// Value is the dynamic representation of any CBOR data item. Use it when
4// the schema isn't known at compile time, when you need to inspect tags,
5// or when keys aren't strings:
6//
7// v := cbor.decode[cbor.Value](bytes)!
8// match v {
9// cbor.Text { println(v.value) }
10// else { ... }
11// }
12//
13// For known schemas prefer `decode[YourStruct]` — it's faster and avoids
14// the heap allocations of building a Value tree.
15pub type Value = Array
16 | Bool
17 | Bytes
18 | FloatNum
19 | IntNum
20 | Map
21 | Null
22 | Simple
23 | Tag
24 | Text
25 | Undefined
26
27// IntNum holds the full unsigned/negative CBOR integer range. CBOR allows
28// values from -(2^64) to 2^64-1, which exceeds either i64 or u64 alone, so
29// the sign bit is split out and the magnitude carried as u64.
30//
31// for unsigned: negative=false, magnitude=value
32// for negative: negative=true, magnitude=encoded_argument
33// actual integer = -1 - i64(magnitude) (when it fits i64)
34pub struct IntNum {
35pub:
36 negative bool
37 magnitude u64
38}
39
40// Bytes is a CBOR byte string (major type 2).
41pub struct Bytes {
42pub mut:
43 data []u8
44}
45
46// Text is a CBOR text string (major type 3, valid UTF-8).
47pub struct Text {
48pub:
49 value string
50}
51
52// Array holds the elements of a CBOR array (major type 4).
53pub struct Array {
54pub mut:
55 elements []Value
56}
57
58// MapPair represents one key/value entry in a CBOR map. CBOR allows any
59// data item as a key, so we keep an ordered list of pairs rather than
60// using V's `map[K]V`.
61pub struct MapPair {
62pub:
63 key Value
64 value Value
65}
66
67// Map holds the ordered key/value pairs of a CBOR map (major type 5).
68pub struct Map {
69pub mut:
70 pairs []MapPair
71}
72
73// Tag wraps a tagged data item (major type 6). The content is stored in
74// a one-element slice rather than as a `&Value` reference: V can box and
75// recurse a sumtype through a slice, while a direct `&Value` field
76// requires manual heap allocation. Use `tag.content()` to access it.
77pub struct Tag {
78pub:
79 number u64
80 content_box []Value
81}
82
83// content returns the Value enclosed by a Tag, or `Null{}` if missing.
84@[inline]
85pub fn (t &Tag) content() Value {
86 if t.content_box.len > 0 {
87 return t.content_box[0]
88 }
89 return Null{}
90}
91
92// Bool is the wrapped form of CBOR true/false (simple values 20/21).
93pub struct Bool {
94pub:
95 value bool
96}
97
98// Null is the wrapped form of CBOR null (simple value 22).
99pub struct Null {}
100
101// Undefined is the wrapped form of CBOR undefined (simple value 23).
102pub struct Undefined {}
103
104// FloatBits records which IEEE 754 width the float was originally encoded
105// at. The encoder honours this when re-emitting a Value, so round-tripping
106// preserves the original byte width.
107pub enum FloatBits as u8 {
108 @none = 0
109 half = 16
110 single = 32
111 double = 64
112}
113
114// FloatNum is a CBOR floating-point value (major type 7, additional info
115// 25/26/27). `bits` records the wire width for fidelity on re-encoding;
116// the default `.@none` lets the encoder pick the shortest IEEE 754 width
117// that preserves the value (RFC 8949 §4.2.2 preferred serialisation), so
118// hand-built FloatNum literals don't accidentally lock in 8-byte output.
119pub struct FloatNum {
120pub:
121 value f64
122 bits FloatBits = .@none
123}
124
125// Simple is the catch-all for major type 7 simple values 0..255 not
126// otherwise covered by Bool/Null/Undefined.
127pub struct Simple {
128pub:
129 value u8
130}
131
132// new_uint wraps a u64 in a Value (unsigned-int variant).
133@[inline]
134pub fn new_uint(n u64) Value {
135 return IntNum{
136 negative: false
137 magnitude: n
138 }
139}
140
141// new_int wraps a signed i64 in a Value, picking unsigned vs negative.
142@[inline]
143pub fn new_int(n i64) Value {
144 if n >= 0 {
145 return IntNum{
146 negative: false
147 magnitude: u64(n)
148 }
149 }
150 return IntNum{
151 negative: true
152 magnitude: u64(-(n + 1))
153 }
154}
155
156// new_negative wraps the encoded argument of a major-type-1 value, where
157// the actual integer is -1 - magnitude. Useful when magnitude exceeds i64.
158@[inline]
159pub fn new_negative(magnitude u64) Value {
160 return IntNum{
161 negative: true
162 magnitude: magnitude
163 }
164}
165
166// new_text wraps a string as a CBOR text Value.
167@[inline]
168pub fn new_text(s string) Value {
169 return Text{
170 value: s
171 }
172}
173
174// new_bytes wraps a []u8 as a CBOR byte-string Value.
175@[inline]
176pub fn new_bytes(b []u8) Value {
177 return Bytes{
178 data: b
179 }
180}
181
182// new_float wraps an f64 as a CBOR FloatNum that re-encodes at full
183// precision unless `f64_to_half` / f32 conversion is lossless.
184@[inline]
185pub fn new_float(v f64) Value {
186 return FloatNum{
187 value: v
188 bits: .@none
189 }
190}
191
192// new_tag wraps an existing Value with a tag number.
193@[inline]
194pub fn new_tag(number u64, content Value) Value {
195 return Tag{
196 number: number
197 content_box: [content]
198 }
199}
200
201// is_nil returns true if v is the CBOR `null` value.
202@[inline]
203pub fn (v &Value) is_nil() bool {
204 return v is Null
205}
206
207// is_undefined returns true if v is the CBOR `undefined` value.
208@[inline]
209pub fn (v &Value) is_undefined() bool {
210 return v is Undefined
211}
212
213// as_int returns the value as an i64 when it fits, or none otherwise.
214// Returns none for FloatNum, Text, etc.
215//
216// CBOR negative integers represent `-1 - magnitude`, so magnitude
217// `2^63 - 1` maps to i64::min and magnitude `2^63` would map to
218// `-2^63 - 1` — outside i64 range — hence the strict ">" cutoff for
219// negatives. Use `as_uint` plus the `negative` flag to recover the
220// full -2^64..2^64-1 CBOR range.
221pub fn (v &Value) as_int() ?i64 {
222 if v is IntNum {
223 if v.negative {
224 if v.magnitude > u64(max_i64) {
225 return none
226 }
227 return -1 - i64(v.magnitude)
228 }
229 if v.magnitude > u64(max_i64) {
230 return none
231 }
232 return i64(v.magnitude)
233 }
234 return none
235}
236
237// as_uint returns the value as a u64 if it's a non-negative integer, else none.
238pub fn (v &Value) as_uint() ?u64 {
239 if v is IntNum {
240 if v.negative {
241 return none
242 }
243 return v.magnitude
244 }
245 return none
246}
247
248// as_float returns the f64 value, or none if v isn't a FloatNum.
249pub fn (v &Value) as_float() ?f64 {
250 if v is FloatNum {
251 return v.value
252 }
253 return none
254}
255
256// as_bool returns the boolean value, or none if v isn't a Bool.
257pub fn (v &Value) as_bool() ?bool {
258 if v is Bool {
259 return v.value
260 }
261 return none
262}
263
264// as_string returns the text-string value, or none if v isn't Text.
265pub fn (v &Value) as_string() ?string {
266 if v is Text {
267 return v.value
268 }
269 return none
270}
271
272// as_bytes returns the byte-string payload, or none if v isn't Bytes.
273pub fn (v &Value) as_bytes() ?[]u8 {
274 if v is Bytes {
275 return v.data
276 }
277 return none
278}
279
280// as_array returns the elements of an Array, or none.
281pub fn (v &Value) as_array() ?[]Value {
282 if v is Array {
283 return v.elements
284 }
285 return none
286}
287
288// as_map returns the pairs of a Map, or none.
289pub fn (v &Value) as_map() ?[]MapPair {
290 if v is Map {
291 return v.pairs
292 }
293 return none
294}
295
296// as_tag returns (number, content) of a Tag, or none.
297pub fn (v &Value) as_tag() ?(u64, Value) {
298 if v is Tag {
299 return v.number, v.content()
300 }
301 return none
302}
303
304// get does a linear lookup of a string-keyed entry in a Map.
305// O(n) — for hot paths decode into a typed struct or `map[string]V`.
306pub fn (v &Value) get(key string) ?Value {
307 if v is Map {
308 for pair in v.pairs {
309 if pair.key is Text {
310 if pair.key.value == key {
311 return pair.value
312 }
313 }
314 }
315 }
316 return none
317}
318
319// at returns the element at `index` of an Array.
320pub fn (v &Value) at(index int) ?Value {
321 if v is Array {
322 if index >= 0 && index < v.elements.len {
323 return v.elements[index]
324 }
325 }
326 return none
327}
328
329// len returns the length of an Array, Map, Text, or Bytes value, or 0.
330pub fn (v &Value) len() int {
331 match v {
332 Array { return v.elements.len }
333 Map { return v.pairs.len }
334 Text { return v.value.len }
335 Bytes { return v.data.len }
336 else { return 0 }
337 }
338}
339