v / vlib / encoding / cbor / tests / generic_test.v
284 lines · 248 sloc · 7.28 KB · 468855eef1db0ff73c62be2d1bf176ffa0e1478e
Raw
1// Generic encode[T]/decode[T] coverage. Exercises every supported V
2// type family — primitives, arrays, maps, structs (with attributes),
3// optional fields, enums, RawMessage, Marshaler/Unmarshaler — and
4// asserts byte-exact output for at least one case per family so we
5// catch silent encoding drift.
6module main
7
8import encoding.cbor
9import encoding.hex
10
11fn h(s string) []u8 {
12 return hex.decode(s) or { panic('invalid hex: ${s}') }
13}
14
15fn beq(a []u8, b []u8) bool {
16 if a.len != b.len {
17 return false
18 }
19 for i in 0 .. a.len {
20 if a[i] != b[i] {
21 return false
22 }
23 }
24 return true
25}
26
27// ---------------------------------------------------------------------
28// Primitive round-trips
29// ---------------------------------------------------------------------
30
31fn test_round_trip_primitives() {
32 bytes_bool := cbor.encode[bool](true, cbor.EncodeOpts{})!
33 assert cbor.decode[bool](bytes_bool, cbor.DecodeOpts{})! == true
34
35 bytes_i32 := cbor.encode[i32](-42, cbor.EncodeOpts{})!
36 assert cbor.decode[i32](bytes_i32, cbor.DecodeOpts{})! == -42
37
38 bytes_u64 := cbor.encode[u64](u64(0x1234_5678_9abc_def0), cbor.EncodeOpts{})!
39 assert cbor.decode[u64](bytes_u64, cbor.DecodeOpts{})! == 0x1234_5678_9abc_def0
40
41 bytes_f64 := cbor.encode[f64](3.141592653589793, cbor.EncodeOpts{})!
42 assert cbor.decode[f64](bytes_f64, cbor.DecodeOpts{})! == 3.141592653589793
43
44 bytes_str := cbor.encode[string]('hello, 世界', cbor.EncodeOpts{})!
45 assert cbor.decode[string](bytes_str, cbor.DecodeOpts{})! == 'hello, 世界'
46}
47
48// ---------------------------------------------------------------------
49// Arrays and maps
50// ---------------------------------------------------------------------
51
52fn test_array_round_trip() {
53 src := [1, 2, 3, 4, 5]
54 bytes := cbor.encode[[]int](src, cbor.EncodeOpts{})!
55 got := cbor.decode[[]int](bytes, cbor.DecodeOpts{})!
56 assert got == src
57}
58
59fn test_map_round_trip() {
60 src := {
61 'a': 1
62 'b': 2
63 'c': 3
64 }
65 bytes := cbor.encode[map[string]int](src, cbor.EncodeOpts{})!
66 got := cbor.decode[map[string]int](bytes, cbor.DecodeOpts{})!
67 for k, v in src {
68 assert got[k] == v
69 }
70 assert got.len == src.len
71}
72
73fn test_nested_array_map() {
74 src := [
75 {
76 'k': 'v1'
77 },
78 {
79 'k': 'v2'
80 },
81 ]
82 bytes := cbor.encode[[]map[string]string](src, cbor.EncodeOpts{})!
83 got := cbor.decode[[]map[string]string](bytes, cbor.DecodeOpts{})!
84 assert got.len == 2
85 assert got[0]['k'] == 'v1'
86 assert got[1]['k'] == 'v2'
87}
88
89// ---------------------------------------------------------------------
90// Structs — attributes, optional, rename strategies
91// ---------------------------------------------------------------------
92
93struct Person {
94 name string
95 age int
96}
97
98fn test_struct_basic() {
99 p := Person{
100 name: 'Alice'
101 age: 42
102 }
103 bytes := cbor.encode[Person](p, cbor.EncodeOpts{})!
104 got := cbor.decode[Person](bytes, cbor.DecodeOpts{})!
105 assert got.name == 'Alice'
106 assert got.age == 42
107}
108
109struct WithAttrs {
110 user_id string @[cbor: 'uid']
111 password string @[skip]
112 internal string @[cbor: '-']
113 keep string
114}
115
116fn test_struct_attributes() {
117 p := WithAttrs{
118 user_id: 'u-1'
119 password: 'secret'
120 internal: 'hidden'
121 keep: 'visible'
122 }
123 bytes := cbor.encode[WithAttrs](p, cbor.EncodeOpts{})!
124 // Decode generically to inspect structure.
125 v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{})!
126 assert v is cbor.Map
127 if v is cbor.Map {
128 mut keys := []string{}
129 for pair in v.pairs {
130 if pair.key is cbor.Text {
131 keys << pair.key.value
132 }
133 }
134 assert 'uid' in keys
135 assert 'keep' in keys
136 assert 'password' !in keys
137 assert 'internal' !in keys
138 }
139 got := cbor.decode[WithAttrs](bytes, cbor.DecodeOpts{})!
140 assert got.user_id == 'u-1'
141 assert got.keep == 'visible'
142}
143
144struct WithOption {
145 name string
146 tag ?string
147}
148
149fn test_struct_option_field() {
150 none_p := WithOption{
151 name: 'a'
152 tag: none
153 }
154 bytes := cbor.encode[WithOption](none_p, cbor.EncodeOpts{})!
155 got := cbor.decode[WithOption](bytes, cbor.DecodeOpts{})!
156 assert got.name == 'a'
157 assert got.tag == none
158
159 some_p := WithOption{
160 name: 'b'
161 tag: ?string('hot')
162 }
163 bytes2 := cbor.encode[WithOption](some_p, cbor.EncodeOpts{})!
164 got2 := cbor.decode[WithOption](bytes2, cbor.DecodeOpts{})!
165 assert got2.name == 'b'
166 assert got2.tag != none
167 assert got2.tag or { '' } == 'hot'
168}
169
170@[cbor_rename_all: 'kebab-case']
171struct WithRename {
172 user_name string
173 user_age int
174}
175
176fn test_struct_rename_all() {
177 p := WithRename{
178 user_name: 'Bob'
179 user_age: 30
180 }
181 bytes := cbor.encode[WithRename](p, cbor.EncodeOpts{})!
182 v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{})!
183 if v is cbor.Map {
184 mut keys := []string{}
185 for pair in v.pairs {
186 if pair.key is cbor.Text {
187 keys << pair.key.value
188 }
189 }
190 assert 'user-name' in keys
191 assert 'user-age' in keys
192 }
193 // And the rename round-trips.
194 got := cbor.decode[WithRename](bytes, cbor.DecodeOpts{})!
195 assert got.user_name == 'Bob'
196 assert got.user_age == 30
197}
198
199// ---------------------------------------------------------------------
200// Enum
201// ---------------------------------------------------------------------
202
203enum Color {
204 red
205 green
206 blue
207}
208
209fn test_enum_round_trip() {
210 bytes := cbor.encode[Color](Color.green, cbor.EncodeOpts{})!
211 got := cbor.decode[Color](bytes, cbor.DecodeOpts{})!
212 assert got == Color.green
213}
214
215// ---------------------------------------------------------------------
216// RawMessage — preserves bytes byte-for-byte
217// ---------------------------------------------------------------------
218
219fn test_raw_message_round_trip() {
220 original := h('a26161016162820203')
221 raw := cbor.decode[cbor.RawMessage](original, cbor.DecodeOpts{})!
222 again := cbor.encode[cbor.RawMessage](raw, cbor.EncodeOpts{})!
223 assert beq(again, original)
224}
225
226// ---------------------------------------------------------------------
227// Marshaler / Unmarshaler — user-controlled wire format
228// ---------------------------------------------------------------------
229
230struct Ipv4 {
231mut:
232 octets [4]u8
233}
234
235pub fn (ip Ipv4) to_cbor() []u8 {
236 mut p := cbor.new_packer(cbor.EncodeOpts{ initial_cap: 8 })
237 p.pack_bytes([ip.octets[0], ip.octets[1], ip.octets[2], ip.octets[3]])
238 return p.bytes().clone()
239}
240
241pub fn (mut ip Ipv4) from_cbor(data []u8) ! {
242 mut u := cbor.new_unpacker(data, cbor.DecodeOpts{})
243 bytes := u.unpack_bytes()!
244 if bytes.len != 4 {
245 return error('Ipv4: expected 4 bytes, got ${bytes.len}')
246 }
247 ip.octets[0] = bytes[0]
248 ip.octets[1] = bytes[1]
249 ip.octets[2] = bytes[2]
250 ip.octets[3] = bytes[3]
251}
252
253fn test_marshaler_round_trip() {
254 ip := Ipv4{
255 octets: [u8(192), 168, 1, 1]!
256 }
257 bytes := cbor.encode[Ipv4](ip, cbor.EncodeOpts{})!
258 // Wire bytes: 0x44 (bytes len 4) followed by 4 octets.
259 assert beq(bytes, h('44c0a80101'))
260 got := cbor.decode[Ipv4](bytes, cbor.DecodeOpts{})!
261 assert got.octets[0] == 192
262 assert got.octets[1] == 168
263 assert got.octets[2] == 1
264 assert got.octets[3] == 1
265}
266
267// ---------------------------------------------------------------------
268// Integer-range checks on decode
269// ---------------------------------------------------------------------
270
271fn test_int_range_overflow_rejected() {
272 // 256 doesn't fit u8.
273 bytes := cbor.encode[u16](u16(256), cbor.EncodeOpts{})!
274 if _ := cbor.decode[u8](bytes, cbor.DecodeOpts{}) {
275 assert false, 'expected u8 range error'
276 }
277}
278
279fn test_negative_to_unsigned_rejected() {
280 bytes := cbor.encode[i64](-1, cbor.EncodeOpts{})!
281 if _ := cbor.decode[u64](bytes, cbor.DecodeOpts{}) {
282 assert false, 'expected u64 range error'
283 }
284}
285