protobuf codec with static type inference jsr.io/@mary/protobuf
typescript jsr

chore: expand on the tests

mary.my.id 868229ea 0de805cd

verified
Changed files
+568 -264
lib
+568 -264
lib/mod.test.ts
··· 9 9 const Message = p.message({ text: p.string() }, { text: 1 }); 10 10 11 11 const cases = [ 12 - '', 13 - 'hello world', 14 - 'hello 🚀', 15 - 'a'.repeat(1000), 16 - 'Café', 17 - 'おはようございます☀️', 18 - 'नमस्ते', 19 - 'Здравствуйте', 20 - '你'.repeat(43), 21 - '🌟'.repeat(32), 22 - '🚀🌟💻', 23 - '🏳️‍🌈🏳️‍⚧️', 12 + { 13 + text: '', 14 + expected: Uint8Array.from([0x0a, 0x00]), 15 + }, // field 1, length 0 16 + { 17 + text: 'hello world', 18 + expected: Uint8Array.from([ 19 + 0x0a, 20 + 0x0b, 21 + 0x68, 22 + 0x65, 23 + 0x6c, 24 + 0x6c, 25 + 0x6f, 26 + 0x20, 27 + 0x77, 28 + 0x6f, 29 + 0x72, 30 + 0x6c, 31 + 0x64, 32 + ]), 33 + }, // field 1, length 11 34 + { 35 + text: 'hello 🚀', 36 + expected: Uint8Array.from([0x0a, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0xf0, 0x9f, 0x9a, 0x80]), 37 + }, // field 1, length 10, UTF-8 rocket 38 + { 39 + text: 'Café', 40 + expected: Uint8Array.from([0x0a, 0x05, 0x43, 0x61, 0x66, 0xc3, 0xa9]), 41 + }, // field 1, length 5, UTF-8 café 42 + 43 + { 44 + text: 'a'.repeat(1000), 45 + expected: null, 46 + }, 47 + { 48 + text: 'おはようございます☀️', 49 + expected: null, 50 + }, 51 + { 52 + text: 'नमस्ते', 53 + expected: null, 54 + }, 55 + { 56 + text: 'Здравствуйте', 57 + expected: null, 58 + }, 59 + { 60 + text: '你'.repeat(43), 61 + expected: null, 62 + }, 63 + { 64 + text: '🌟'.repeat(32), 65 + expected: null, 66 + }, 67 + { 68 + text: '🚀🌟💻', 69 + expected: null, 70 + }, 71 + { 72 + text: '🏳️‍🌈🏳️‍⚧️', 73 + expected: null, 74 + }, 24 75 ]; 25 76 26 - for (const text of cases) { 77 + for (const { text, expected } of cases) { 27 78 const encoded = p.encode(Message, { text }); 28 - const decoded = p.decode(Message, encoded); 79 + if (expected !== null) { 80 + assertEquals(encoded, expected); 81 + } 29 82 83 + const decoded = p.decode(Message, encoded); 30 84 assertEquals(decoded, { text }); 31 85 } 32 86 }); ··· 35 89 const Message = p.message({ value: p.int32() }, { value: 1 }); 36 90 37 91 const cases = [ 38 - 0, 39 - 1, 40 - -1, 41 - 127, 42 - -128, 43 - 255, 44 - -256, 45 - 32767, 46 - -32768, 47 - 65535, 48 - -65536, 49 - 2147483647, // max int32 50 - -2147483648, // min int32 92 + { // field 1, varint 0 93 + value: 0, 94 + expected: Uint8Array.from([0x08, 0x00]), 95 + }, 96 + { // field 1, varint 1 97 + value: 1, 98 + expected: Uint8Array.from([0x08, 0x01]), 99 + }, 100 + { // field 1, varint -1 (int32) 101 + value: -1, 102 + expected: Uint8Array.from([0x08, 0xff, 0xff, 0xff, 0xff, 0x0f]), 103 + }, 104 + { // field 1, varint 127 105 + value: 127, 106 + expected: Uint8Array.from([0x08, 0x7f]), 107 + }, 108 + { // field 1, varint -128 109 + value: -128, 110 + expected: Uint8Array.from([0x08, 0x80, 0xff, 0xff, 0xff, 0x0f]), 111 + }, 112 + { // field 1, varint 255 113 + value: 255, 114 + expected: Uint8Array.from([0x08, 0xff, 0x01]), 115 + }, 116 + { // field 1, varint -256 117 + value: -256, 118 + expected: Uint8Array.from([0x08, 0x80, 0xfe, 0xff, 0xff, 0x0f]), 119 + }, 120 + { // field 1, varint 32767 121 + value: 32767, 122 + expected: Uint8Array.from([0x08, 0xff, 0xff, 0x01]), 123 + }, 124 + { // field 1, varint -32768 125 + value: -32768, 126 + expected: Uint8Array.from([0x08, 0x80, 0x80, 0xfe, 0xff, 0x0f]), 127 + }, 128 + { // field 1, varint 65535 129 + value: 65535, 130 + expected: Uint8Array.from([0x08, 0xff, 0xff, 0x03]), 131 + }, 132 + { // field 1, varint -65536 133 + value: -65536, 134 + expected: Uint8Array.from([0x08, 0x80, 0x80, 0xfc, 0xff, 0x0f]), 135 + }, 136 + { // max int32 137 + value: 2147483647, 138 + expected: null, 139 + }, 140 + { // min int32 141 + value: -2147483648, 142 + expected: null, 143 + }, 51 144 ]; 52 145 53 - for (const value of cases) { 146 + for (const { value, expected } of cases) { 54 147 const encoded = p.encode(Message, { value }); 55 - const decoded = p.decode(Message, encoded); 148 + if (expected !== null) { 149 + assertEquals(encoded, expected); 150 + } 56 151 152 + const decoded = p.decode(Message, encoded); 57 153 assertEquals(decoded, { value }); 58 154 } 59 155 }); ··· 62 158 const Message = p.message({ value: p.int64() }, { value: 1 }); 63 159 64 160 const cases = [ 65 - 0n, 66 - 1n, 67 - -1n, 68 - 127n, 69 - -128n, 70 - 9223372036854775807n, // max int64 71 - -9223372036854775808n, // min int64 161 + { // field 1, varint 0 162 + value: 0n, 163 + expected: Uint8Array.from([0x08, 0x00]), 164 + }, 165 + { // field 1, varint 1 166 + value: 1n, 167 + expected: Uint8Array.from([0x08, 0x01]), 168 + }, 169 + { // field 1, varint -1 (int64) 170 + value: -1n, 171 + expected: Uint8Array.from([0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]), 172 + }, 173 + { // field 1, varint 127 174 + value: 127n, 175 + expected: Uint8Array.from([0x08, 0x7f]), 176 + }, 177 + { // field 1, varint -128 178 + value: -128n, 179 + expected: Uint8Array.from([0x08, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]), 180 + }, 181 + { // max int64 182 + value: 9223372036854775807n, 183 + expected: null, 184 + }, 185 + { // min int64 186 + value: -9223372036854775808n, 187 + expected: null, 188 + }, 72 189 ]; 73 190 74 - for (const value of cases) { 191 + for (const { value, expected } of cases) { 75 192 const encoded = p.encode(Message, { value }); 76 - const decoded = p.decode(Message, encoded); 193 + if (expected !== null) { 194 + assertEquals(encoded, expected); 195 + } 77 196 197 + const decoded = p.decode(Message, encoded); 78 198 assertEquals(decoded, { value }); 79 199 } 80 200 }); ··· 83 203 const Message = p.message({ value: p.uint32() }, { value: 1 }); 84 204 85 205 const cases = [ 86 - 0, 87 - 1, 88 - 127, 89 - 255, 90 - 32767, 91 - 65535, 92 - 2147483647, 93 - 4294967295, // max uint32 206 + { value: 0, expected: Uint8Array.from([0x08, 0x00]) }, // field 1, varint 0 207 + { value: 1, expected: Uint8Array.from([0x08, 0x01]) }, // field 1, varint 1 208 + { value: 127, expected: Uint8Array.from([0x08, 0x7f]) }, // field 1, varint 127 209 + { value: 255, expected: Uint8Array.from([0x08, 0xff, 0x01]) }, // field 1, varint 255 210 + { value: 32767, expected: Uint8Array.from([0x08, 0xff, 0xff, 0x01]) }, // field 1, varint 32767 211 + { value: 65535, expected: Uint8Array.from([0x08, 0xff, 0xff, 0x03]) }, // field 1, varint 65535 212 + { value: 2147483647, expected: Uint8Array.from([0x08, 0xff, 0xff, 0xff, 0xff, 0x07]) }, // field 1, varint max int32 213 + { value: 4294967295, expected: Uint8Array.from([0x08, 0xff, 0xff, 0xff, 0xff, 0x0f]) }, // max uint32 94 214 ]; 95 215 96 - for (const value of cases) { 216 + for (const { value, expected } of cases) { 97 217 const encoded = p.encode(Message, { value }); 98 - const decoded = p.decode(Message, encoded); 218 + if (expected !== null) { 219 + assertEquals(encoded, expected); 220 + } 99 221 222 + const decoded = p.decode(Message, encoded); 100 223 assertEquals(decoded, { value }); 101 224 } 102 225 }); ··· 105 228 const Message = p.message({ value: p.uint64() }, { value: 1 }); 106 229 107 230 const cases = [ 108 - 0n, 109 - 1n, 110 - 127n, 111 - 255n, 112 - 18446744073709551615n, // max uint64 231 + { value: 0n, expected: Uint8Array.from([0x08, 0x00]) }, // field 1, varint 0 232 + { value: 1n, expected: Uint8Array.from([0x08, 0x01]) }, // field 1, varint 1 233 + { value: 127n, expected: Uint8Array.from([0x08, 0x7f]) }, // field 1, varint 127 234 + { value: 255n, expected: Uint8Array.from([0x08, 0xff, 0x01]) }, // field 1, varint 255 235 + { value: 18446744073709551615n, expected: null }, // max uint64 113 236 ]; 114 237 115 - for (const value of cases) { 238 + for (const { value, expected } of cases) { 116 239 const encoded = p.encode(Message, { value }); 117 - const decoded = p.decode(Message, encoded); 240 + if (expected !== null) { 241 + assertEquals(encoded, expected); 242 + } 118 243 244 + const decoded = p.decode(Message, encoded); 119 245 assertEquals(decoded, { value }); 120 246 } 121 247 }); ··· 124 250 const schema = p.message({ value: p.sint32() }, { value: 1 }); 125 251 126 252 const testCases = [ 127 - 0, 128 - 1, 129 - -1, 130 - 2, 131 - -2, 132 - 127, 133 - -128, 134 - 2147483647, // max int32 135 - -2147483648, // min int32 253 + { value: 0, expected: Uint8Array.from([0x08, 0x00]) }, // field 1, zigzag 0 -> varint 0 254 + { value: 1, expected: Uint8Array.from([0x08, 0x02]) }, // field 1, zigzag 1 -> varint 2 255 + { value: -1, expected: Uint8Array.from([0x08, 0x01]) }, // field 1, zigzag -1 -> varint 1 256 + { value: 2, expected: Uint8Array.from([0x08, 0x04]) }, // field 1, zigzag 2 -> varint 4 257 + { value: -2, expected: Uint8Array.from([0x08, 0x03]) }, // field 1, zigzag -2 -> varint 3 258 + { value: 127, expected: Uint8Array.from([0x08, 0xfe, 0x01]) }, // field 1, zigzag 127 -> varint 254 259 + { value: -128, expected: Uint8Array.from([0x08, 0xff, 0x01]) }, // field 1, zigzag -128 -> varint 255 260 + { value: 2147483647, expected: null }, // max int32 261 + { value: -2147483648, expected: null }, // min int32 136 262 ]; 137 263 138 - for (const value of testCases) { 264 + for (const { value, expected } of testCases) { 139 265 const encoded = p.encode(schema, { value }); 140 - const decoded = p.decode(schema, encoded); 266 + if (expected !== null) { 267 + assertEquals(encoded, expected); 268 + } 141 269 270 + const decoded = p.decode(schema, encoded); 142 271 assertEquals(decoded, { value }); 143 272 } 144 273 }); ··· 147 276 const Message = p.message({ value: p.sint64() }, { value: 1 }); 148 277 149 278 const cases = [ 150 - 0n, 151 - 1n, 152 - -1n, 153 - 2n, 154 - -2n, 155 - 127n, 156 - -128n, 157 - 9223372036854775807n, // max int64 158 - -9223372036854775808n, // min int64 279 + { value: 0n, expected: Uint8Array.from([0x08, 0x00]) }, // field 1, zigzag 0 -> varint 0 280 + { value: 1n, expected: Uint8Array.from([0x08, 0x02]) }, // field 1, zigzag 1 -> varint 2 281 + { value: -1n, expected: Uint8Array.from([0x08, 0x01]) }, // field 1, zigzag -1 -> varint 1 282 + { value: 2n, expected: Uint8Array.from([0x08, 0x04]) }, // field 1, zigzag 2 -> varint 4 283 + { value: -2n, expected: Uint8Array.from([0x08, 0x03]) }, // field 1, zigzag -2 -> varint 3 284 + { value: 127n, expected: Uint8Array.from([0x08, 0xfe, 0x01]) }, // field 1, zigzag 127 -> varint 254 285 + { value: -128n, expected: Uint8Array.from([0x08, 0xff, 0x01]) }, // field 1, zigzag -128 -> varint 255 286 + { value: 9223372036854775807n, expected: null }, // max int64 287 + { value: -9223372036854775808n, expected: null }, // min int64 159 288 ]; 160 289 161 - for (const value of cases) { 290 + for (const { value, expected } of cases) { 162 291 const encoded = p.encode(Message, { value }); 163 - const decoded = p.decode(Message, encoded); 292 + if (expected !== null) { 293 + assertEquals(encoded, expected); 294 + } 164 295 296 + const decoded = p.decode(Message, encoded); 165 297 assertEquals(decoded, { value }); 166 298 } 167 299 }); ··· 170 302 const Message = p.message({ value: p.float() }, { value: 1 }); 171 303 172 304 const cases = [ 173 - 0.0, 174 - 1.0, 175 - -1.0, 176 - 3.14159, 177 - -3.14159, 178 - 1.5e10, 179 - -1.5e10, 180 - 3.4028235e38, // close to max float32 181 - 1.175494e-38, // close to min positive float32 182 - Infinity, 183 - -Infinity, 184 - NaN, 305 + { value: 0.0, expected: Uint8Array.from([0x0d, 0x00, 0x00, 0x00, 0x00]) }, // field 1, float32 0.0 (little-endian) 306 + { value: 1.0, expected: Uint8Array.from([0x0d, 0x00, 0x00, 0x80, 0x3f]) }, // field 1, float32 1.0 (little-endian) 307 + { value: -1.0, expected: Uint8Array.from([0x0d, 0x00, 0x00, 0x80, 0xbf]) }, // field 1, float32 -1.0 (little-endian) 308 + { value: 3.14159, expected: null }, 309 + { value: -3.14159, expected: null }, 310 + { value: 1.5e10, expected: null }, 311 + { value: -1.5e10, expected: null }, 312 + { value: 3.4028235e38, expected: null }, // close to max float32 313 + { value: 1.175494e-38, expected: null }, // close to min positive float32 314 + { value: Infinity, expected: null }, 315 + { value: -Infinity, expected: null }, 316 + { value: NaN, expected: null }, 185 317 ]; 186 318 187 - for (const value of cases) { 319 + for (const { value, expected } of cases) { 188 320 const encoded = p.encode(Message, { value }); 321 + if (expected !== null) { 322 + assertEquals(encoded, expected); 323 + } 324 + 189 325 const decoded = p.decode(Message, encoded); 190 326 191 327 // Special handling for infinity values ··· 205 341 const Message = p.message({ value: p.double() }, { value: 1 }); 206 342 207 343 const cases = [ 208 - 0.0, 209 - 1.0, 210 - -1.0, 211 - 3.141592653589793, 212 - -3.141592653589793, 213 - 1.7976931348623157e+308, // close to max double 214 - 2.2250738585072014e-308, // close to min positive double 215 - Infinity, 216 - -Infinity, 217 - NaN, 344 + { // field 1, double 0.0 (little-endian) 345 + value: 0.0, 346 + expected: Uint8Array.from([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 347 + }, 348 + { // field 1, double 1.0 (little-endian) 349 + value: 1.0, 350 + expected: Uint8Array.from([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f]), 351 + }, 352 + { // field 1, double -1.0 (little-endian) 353 + value: -1.0, 354 + expected: Uint8Array.from([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xbf]), 355 + }, 356 + { 357 + value: 3.141592653589793, 358 + expected: null, 359 + }, 360 + { 361 + value: -3.141592653589793, 362 + expected: null, 363 + }, 364 + { // close to max double 365 + value: 1.7976931348623157e+308, 366 + expected: null, 367 + }, 368 + { // close to min positive double 369 + value: 2.2250738585072014e-308, 370 + expected: null, 371 + }, 372 + { 373 + value: Infinity, 374 + expected: null, 375 + }, 376 + { 377 + value: -Infinity, 378 + expected: null, 379 + }, 380 + { 381 + value: NaN, 382 + expected: null, 383 + }, 218 384 ]; 219 385 220 - for (const value of cases) { 386 + for (const { value, expected } of cases) { 221 387 const encoded = p.encode(Message, { value }); 222 - const decoded = p.decode(Message, encoded); 388 + if (expected !== null) { 389 + assertEquals(encoded, expected); 390 + } 223 391 392 + const decoded = p.decode(Message, encoded); 224 393 assertEquals(decoded, { value }); 225 394 } 226 395 }); ··· 283 452 Deno.test('boolean encoding/decoding', () => { 284 453 const Message = p.message({ value: p.boolean() }, { value: 1 }); 285 454 286 - const cases = [true, false]; 455 + const cases = [ 456 + { value: true, expected: Uint8Array.from([0x08, 0x01]) }, // field 1, varint 1 457 + { value: false, expected: Uint8Array.from([0x08, 0x00]) }, // field 1, varint 0 458 + ]; 287 459 288 - for (const value of cases) { 460 + for (const { value, expected } of cases) { 289 461 const encoded = p.encode(Message, { value }); 462 + assertEquals(encoded, expected); 463 + 290 464 const decoded = p.decode(Message, encoded); 291 - 292 465 assertEquals(decoded, { value }); 293 466 } 294 467 }); ··· 297 470 const Message = p.message({ data: p.bytes() }, { data: 1 }); 298 471 299 472 const cases = [ 300 - Uint8Array.from([]), 301 - Uint8Array.from([0]), 302 - Uint8Array.from([1, 2, 3, 4, 5]), 303 - Uint8Array.from([255, 254, 253]), 304 - new Uint8Array(Array.from({ length: 1000 }, (_, i) => i % 256)), // large array 473 + { // field 1, length 0 474 + data: new Uint8Array(0), 475 + expected: Uint8Array.from([0x0a, 0x00]), 476 + }, 477 + { // field 1, length 1, byte 0 478 + data: Uint8Array.from([0]), 479 + expected: Uint8Array.from([0x0a, 0x01, 0x00]), 480 + }, 481 + { // field 1, length 5 482 + data: Uint8Array.from([1, 2, 3, 4, 5]), 483 + expected: Uint8Array.from([0x0a, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]), 484 + }, 485 + { // field 1, length 3 486 + data: Uint8Array.from([255, 254, 253]), 487 + expected: Uint8Array.from([0x0a, 0x03, 0xff, 0xfe, 0xfd]), 488 + }, 489 + { // large array 490 + data: new Uint8Array(Array.from({ length: 1000 }, (_, i) => i % 256)), 491 + expected: null, 492 + }, 305 493 ]; 306 494 307 - for (const data of cases) { 495 + for (const { data, expected } of cases) { 308 496 const encoded = p.encode(Message, { data }); 309 - const decoded = p.decode(Message, encoded); 497 + if (expected !== null) { 498 + assertEquals(encoded, expected); 499 + } 310 500 501 + const decoded = p.decode(Message, encoded); 311 502 assertEquals(decoded, { data }); 312 503 } 313 504 }); ··· 316 507 const Message = p.message({ value: p.fixed32() }, { value: 1 }); 317 508 318 509 const cases = [ 319 - 0, 320 - 1, 321 - 255, 322 - 65535, 323 - 4294967295, // max uint32 510 + { // field 1, fixed32 0 (little-endian) 511 + value: 0, 512 + expected: Uint8Array.from([0x0d, 0x00, 0x00, 0x00, 0x00]), 513 + }, 514 + { // field 1, fixed32 1 (little-endian) 515 + value: 1, 516 + expected: Uint8Array.from([0x0d, 0x01, 0x00, 0x00, 0x00]), 517 + }, 518 + { // field 1, fixed32 255 (little-endian) 519 + value: 255, 520 + expected: Uint8Array.from([0x0d, 0xff, 0x00, 0x00, 0x00]), 521 + }, 522 + { // field 1, fixed32 65535 (little-endian) 523 + value: 65535, 524 + expected: Uint8Array.from([0x0d, 0xff, 0xff, 0x00, 0x00]), 525 + }, 526 + { // field 1, fixed32 max uint32 (little-endian) 527 + value: 4294967295, 528 + expected: Uint8Array.from([0x0d, 0xff, 0xff, 0xff, 0xff]), 529 + }, 324 530 ]; 325 531 326 - for (const value of cases) { 532 + for (const { value, expected } of cases) { 327 533 const encoded = p.encode(Message, { value }); 534 + assertEquals(encoded, expected); 535 + 328 536 const decoded = p.decode(Message, encoded); 329 - 330 537 assertEquals(decoded, { value }); 331 538 } 332 539 }); ··· 335 542 const Message = p.message({ value: p.fixed64() }, { value: 1 }); 336 543 337 544 const cases = [ 338 - 0n, 339 - 1n, 340 - 255n, 341 - 65535n, 342 - 18446744073709551615n, // max uint64 545 + { // field 1, fixed64 0 (little-endian) 546 + value: 0n, 547 + expected: Uint8Array.from([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 548 + }, 549 + { // field 1, fixed64 1 (little-endian) 550 + value: 1n, 551 + expected: Uint8Array.from([0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 552 + }, 553 + { // field 1, fixed64 255 (little-endian) 554 + value: 255n, 555 + expected: Uint8Array.from([0x09, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 556 + }, 557 + { // field 1, fixed64 65535 (little-endian) 558 + value: 65535n, 559 + expected: Uint8Array.from([0x09, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 560 + }, 561 + { // field 1, fixed64 max uint64 (little-endian) 562 + value: 18446744073709551615n, 563 + expected: Uint8Array.from([0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 564 + }, 343 565 ]; 344 566 345 - for (const value of cases) { 567 + for (const { value, expected } of cases) { 346 568 const encoded = p.encode(Message, { value }); 569 + assertEquals(encoded, expected); 570 + 347 571 const decoded = p.decode(Message, encoded); 348 - 349 572 assertEquals(decoded, { value }); 350 573 } 351 574 }); ··· 354 577 const Message = p.message({ value: p.sfixed32() }, { value: 1 }); 355 578 356 579 const cases = [ 357 - 0, 358 - 1, 359 - -1, 360 - 2147483647, // max int32 361 - -2147483648, // min int32 580 + { value: 0, expected: Uint8Array.from([0x0d, 0x00, 0x00, 0x00, 0x00]) }, // field 1, sfixed32 0 (little-endian) 581 + { value: 1, expected: Uint8Array.from([0x0d, 0x01, 0x00, 0x00, 0x00]) }, // field 1, sfixed32 1 (little-endian) 582 + { value: -1, expected: Uint8Array.from([0x0d, 0xff, 0xff, 0xff, 0xff]) }, // field 1, sfixed32 -1 (little-endian, two's complement) 583 + { value: 2147483647, expected: Uint8Array.from([0x0d, 0xff, 0xff, 0xff, 0x7f]) }, // field 1, sfixed32 max int32 (little-endian) 584 + { value: -2147483648, expected: Uint8Array.from([0x0d, 0x00, 0x00, 0x00, 0x80]) }, // field 1, sfixed32 min int32 (little-endian) 362 585 ]; 363 586 364 - for (const value of cases) { 587 + for (const { value, expected } of cases) { 365 588 const encoded = p.encode(Message, { value }); 366 - const decoded = p.decode(Message, encoded); 589 + assertEquals(encoded, expected); 367 590 591 + const decoded = p.decode(Message, encoded); 368 592 assertEquals(decoded, { value }); 369 593 } 370 594 }); ··· 373 597 const Message = p.message({ value: p.sfixed64() }, { value: 1 }); 374 598 375 599 const cases = [ 376 - 0n, 377 - 1n, 378 - -1n, 379 - 9223372036854775807n, // max int64 380 - -9223372036854775808n, // min int64 600 + { // field 1, sfixed64 0 (little-endian) 601 + value: 0n, 602 + expected: Uint8Array.from([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 603 + }, 604 + { // field 1, sfixed64 1 (little-endian) 605 + value: 1n, 606 + expected: Uint8Array.from([0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 607 + }, 608 + { // field 1, sfixed64 -1 (little-endian, two's complement) 609 + value: -1n, 610 + expected: Uint8Array.from([0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 611 + }, 612 + { // field 1, sfixed64 max int64 (little-endian) 613 + value: 9223372036854775807n, 614 + expected: Uint8Array.from([0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]), 615 + }, 616 + { // field 1, sfixed64 min int64 (little-endian) 617 + value: -9223372036854775808n, 618 + expected: Uint8Array.from([0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80]), 619 + }, 381 620 ]; 382 621 383 - for (const value of cases) { 622 + for (const { value, expected } of cases) { 384 623 const encoded = p.encode(Message, { value }); 624 + assertEquals(encoded, expected); 625 + 385 626 const decoded = p.decode(Message, encoded); 386 - 387 627 assertEquals(decoded, { value }); 388 628 } 389 629 }); ··· 395 635 Deno.test('repeated fields', () => { 396 636 const cases = [ 397 637 { 398 - numbers: [], 399 - strings: [], 638 + data: { numbers: [], strings: [] }, 639 + unpacked: new Uint8Array(0), // empty message 640 + packed: Uint8Array.from([0x08, 0x00, 0x12, 0x00]), // field 1: tag + length 0, field 2: tag + length 0 400 641 }, 401 642 { 402 - numbers: [1], 403 - strings: ['hello'], 643 + data: { numbers: [1], strings: ['hello'] }, 644 + unpacked: Uint8Array.from([0x08, 0x01, 0x12, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f]), // field 1: varint 1, field 2: length 5 + "hello" 645 + packed: Uint8Array.from([0x08, 0x01, 0x01, 0x12, 0x06, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f]), // field 1: tag + length 1 + varint 1, field 2: tag + length 6 + (length 5 + "hello") 404 646 }, 405 647 { 406 - numbers: [1, 2, 3, -1, -2], 407 - strings: ['hello', 'world', ''], 648 + data: { numbers: [1, 2, 3], strings: ['hi'] }, 649 + unpacked: Uint8Array.from([0x08, 0x01, 0x08, 0x02, 0x08, 0x03, 0x12, 0x02, 0x68, 0x69]), // field 1: 1,2,3, field 2: "hi" 650 + packed: Uint8Array.from([0x08, 0x03, 0x01, 0x02, 0x03, 0x12, 0x03, 0x02, 0x68, 0x69]), // field 1: tag + length 3 + varints 1,2,3, field 2: tag + length 3 + (length 2 + "hi") 408 651 }, 409 652 { 410 - numbers: Array.from({ length: 100 }, (_, i) => i), 411 - strings: Array.from({ length: 100 }, (_, i) => `item${i}`), 653 + data: { 654 + numbers: Array.from({ length: 100 }, (_, i) => i), 655 + strings: Array.from({ length: 100 }, (_, i) => `item${i}`), 656 + }, 657 + unpacked: null, // too large for expected bytes 658 + packed: null, // too large for expected bytes 412 659 }, 413 660 ]; 414 661 662 + // Test non-packed repeated fields 415 663 { 416 664 const Message = p.message({ 417 665 numbers: p.repeated(p.int32(), false), ··· 421 669 strings: 2, 422 670 }); 423 671 424 - for (const data of cases) { 672 + for (const { data, unpacked } of cases) { 425 673 const encoded = p.encode(Message, data); 674 + if (unpacked !== null) { 675 + assertEquals(encoded, unpacked); 676 + } 677 + 426 678 const decoded = p.decode(Message, encoded); 427 - 428 679 assertEquals(decoded, data); 429 680 } 430 681 } 431 682 683 + // Test packed repeated fields (different wire format) 432 684 { 433 685 const Message = p.message({ 434 686 numbers: p.repeated(p.int32(), true), ··· 438 690 strings: 2, 439 691 }); 440 692 441 - for (const data of cases) { 693 + for (const { data, packed } of cases) { 442 694 const encoded = p.encode(Message, data); 443 - const decoded = p.decode(Message, encoded); 695 + if (packed !== null) { 696 + assertEquals(encoded, packed); 697 + } 444 698 699 + const decoded = p.decode(Message, encoded); 445 700 assertEquals(decoded, data); 446 701 } 447 702 } ··· 460 715 withoutDefault: 4, 461 716 }); 462 717 463 - { 464 - const full = { 465 - required: 'hello', 466 - withDefault: 'custom', 467 - withFunctionDefault: 99, 468 - withoutDefault: 'present', 469 - }; 470 - 471 - const encoded = p.encode(Message, full); 472 - const decoded = p.decode(Message, encoded); 473 - 474 - assertEquals(decoded, full); 475 - } 476 - { 477 - const minimal = { required: 'hello' }; 478 - 479 - const encoded = p.encode(Message, minimal); 480 - const decoded = p.decode(Message, encoded); 481 - 482 - assertEquals(decoded, { 483 - required: 'hello', 484 - withDefault: 'default_value', 485 - withFunctionDefault: 42, 486 - // withoutDefault should be undefined (not present) 487 - }); 488 - } 718 + const cases = [ 719 + { 720 + data: { 721 + required: 'hello', 722 + withDefault: 'custom', 723 + withFunctionDefault: 99, 724 + withoutDefault: 'present', 725 + }, 726 + expected: Uint8Array.from([ 727 + 0x0a, 728 + 0x05, 729 + 0x68, 730 + 0x65, 731 + 0x6c, 732 + 0x6c, 733 + 0x6f, // field 1: "hello" 734 + 0x12, 735 + 0x06, 736 + 0x63, 737 + 0x75, 738 + 0x73, 739 + 0x74, 740 + 0x6f, 741 + 0x6d, // field 2: "custom" 742 + 0x18, 743 + 0x63, // field 3: varint 99 744 + 0x22, 745 + 0x07, 746 + 0x70, 747 + 0x72, 748 + 0x65, 749 + 0x73, 750 + 0x65, 751 + 0x6e, 752 + 0x74, // field 4: "present" 753 + ]), 754 + }, 755 + { 756 + data: { required: 'hello' }, 757 + expected: Uint8Array.from([0x0a, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f]), // field 1: "hello" only 758 + }, 759 + { 760 + data: { 761 + required: 'hello', 762 + withDefault: 'custom_value', 763 + }, 764 + expected: Uint8Array.from([ 765 + 0x0a, 766 + 0x05, 767 + 0x68, 768 + 0x65, 769 + 0x6c, 770 + 0x6c, 771 + 0x6f, // field 1: "hello" 772 + 0x12, 773 + 0x0c, 774 + 0x63, 775 + 0x75, 776 + 0x73, 777 + 0x74, 778 + 0x6f, 779 + 0x6d, 780 + 0x5f, 781 + 0x76, 782 + 0x61, 783 + 0x6c, 784 + 0x75, 785 + 0x65, // field 2: "custom_value" 786 + ]), 787 + }, 788 + ]; 489 789 490 - { 491 - const partial = { 492 - required: 'hello', 493 - withDefault: 'custom_value', 494 - }; 790 + for (const { data, expected } of cases) { 791 + const encoded = p.encode(Message, data); 792 + assertEquals(encoded, expected); 495 793 496 - const encoded = p.encode(Message, partial); 497 794 const decoded = p.decode(Message, encoded); 498 795 499 - assertEquals(decoded, { 500 - required: 'hello', 501 - withDefault: 'custom_value', 502 - withFunctionDefault: 42, 503 - // withoutDefault should be undefined (not present) 504 - }); 796 + // Handle default values in decoded output 797 + if (!data.withDefault && !data.withFunctionDefault && !data.withoutDefault) { 798 + assertEquals(decoded, { 799 + required: data.required, 800 + withDefault: 'default_value', 801 + withFunctionDefault: 42, 802 + // withoutDefault should be undefined (not present) 803 + }); 804 + } else if (!data.withFunctionDefault && !data.withoutDefault) { 805 + assertEquals(decoded, { 806 + required: data.required, 807 + withDefault: data.withDefault, 808 + withFunctionDefault: 42, 809 + // withoutDefault should be undefined (not present) 810 + }); 811 + } else { 812 + assertEquals(decoded, data); 813 + } 505 814 } 506 815 }); 507 816 ··· 511 820 const encoded = p.encode(Message, {}); 512 821 const decoded = p.decode(Message, encoded); 513 822 823 + // Empty message should encode to empty buffer 824 + assertEquals(encoded, new Uint8Array(0)); 514 825 assertEquals(decoded, {}); 515 826 }); 516 827 517 828 Deno.test('nested messages', () => { 518 - const Address = p.message({ 519 - street: p.string(), 520 - city: p.string(), 521 - zipCode: p.optional(p.string()), 522 - }, { 523 - street: 1, 524 - city: 2, 525 - zipCode: 3, 526 - }); 527 - 528 - const Person = p.message({ 529 - name: p.string(), 530 - age: p.int32(), 531 - address: Address, 532 - addresses: p.repeated(Address), 533 - }, { 534 - name: 1, 535 - age: 2, 536 - address: 3, 537 - addresses: 4, 538 - }); 829 + { 830 + const Simple = p.message({ 831 + inner: p.optional(p.message({ value: p.int32() }, { value: 1 })), 832 + }, { inner: 2 }); 539 833 540 - const data = { 541 - name: 'John Doe', 542 - age: 30, 543 - address: { 544 - street: '123 Main St', 545 - city: 'Anytown', 546 - zipCode: '12345', 547 - }, 548 - addresses: [ 834 + const cases = [ 549 835 { 550 - street: '456 Oak Ave', 551 - city: 'Other City', 836 + data: { inner: { value: 42 } }, 837 + expected: Uint8Array.from([0x12, 0x02, 0x08, 0x2a]), // field 2: length 2, field 1: varint 42 552 838 }, 553 839 { 554 - street: '789 Pine Rd', 555 - city: 'Another City', 556 - zipCode: '67890', 840 + data: {}, 841 + expected: new Uint8Array(0), // empty message 842 + }, 843 + ]; 844 + 845 + for (const { data, expected } of cases) { 846 + const encoded = p.encode(Simple, data); 847 + assertEquals(encoded, expected); 848 + 849 + const decoded = p.decode(Simple, encoded); 850 + assertEquals(decoded, data); 851 + } 852 + } 853 + 854 + { 855 + const Address = p.message({ 856 + street: p.string(), 857 + city: p.string(), 858 + zipCode: p.optional(p.string()), 859 + }, { 860 + street: 1, 861 + city: 2, 862 + zipCode: 3, 863 + }); 864 + 865 + const Person = p.message({ 866 + name: p.string(), 867 + age: p.int32(), 868 + address: Address, 869 + addresses: p.repeated(Address), 870 + }, { 871 + name: 1, 872 + age: 2, 873 + address: 3, 874 + addresses: 4, 875 + }); 876 + 877 + const data = { 878 + name: 'John Doe', 879 + age: 30, 880 + address: { 881 + street: '123 Main St', 882 + city: 'Anytown', 883 + zipCode: '12345', 557 884 }, 558 - ], 559 - }; 885 + addresses: [ 886 + { 887 + street: '456 Oak Ave', 888 + city: 'Other City', 889 + }, 890 + { 891 + street: '789 Pine Rd', 892 + city: 'Another City', 893 + zipCode: '67890', 894 + }, 895 + ], 896 + }; 560 897 561 - const encoded = p.encode(Person, data); 562 - const decoded = p.decode(Person, encoded); 898 + const encoded = p.encode(Person, data); 899 + const decoded = p.decode(Person, encoded); 563 900 564 - assertEquals(decoded, data); 901 + assertEquals(decoded, data); 902 + } 565 903 }); 566 904 567 905 Deno.test('self-referential messages', () => { ··· 817 1155 const decoded = p.decode(Message, buffer); 818 1156 819 1157 assertEquals(decoded, { value: 24 }); 820 - }); 821 - 822 - Deno.test('encoding produces correct wire format', () => { 823 - // Test that our encoding matches expected protobuf wire format 824 - const schema = p.message({ 825 - a: p.int32(), 826 - b: p.string(), 827 - }, { 828 - a: 1, 829 - b: 2, 830 - }); 831 - 832 - const encoded = p.encode(schema, { a: 150, b: 'testing' }); 833 - 834 - // Manual verification of wire format: 835 - // Field 1 (a=150): tag=1<<3|0=8, value=150 (varint) = [8, 150, 1] 836 - // Field 2 (b="testing"): tag=2<<3|2=18, length=7, "testing" = [18, 7, 116, 101, 115, 116, 105, 110, 103] 837 - 838 - const expected = Uint8Array.from([ 839 - 8, 840 - 150, 841 - 1, // field 1: int32 value 150 842 - 18, 843 - 7, 844 - 116, 845 - 101, 846 - 115, 847 - 116, 848 - 105, 849 - 110, 850 - 103, // field 2: string "testing" 851 - ]); 852 - 853 - assertEquals(encoded, expected); 854 1158 }); 855 1159 856 1160 // #endregion