protobuf codec with static type inference jsr.io/@mary/protobuf
typescript jsr
at trunk 39 kB view raw
1import { assert, assertAlmostEquals, assertEquals, assertStringIncludes, assertThrows } from '@std/assert'; 2import { nanoid } from 'nanoid/non-secure'; 3 4import * as p from './mod.ts'; 5 6// #region Primitive types 7 8Deno.test('string encoding/decoding', () => { 9 const Message = p.message({ text: p.string() }, { text: 1 }); 10 11 const cases = [ 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 }, 75 ]; 76 77 for (const { text, expected } of cases) { 78 const encoded = p.encode(Message, { text }); 79 if (expected !== null) { 80 assertEquals(encoded, expected); 81 } 82 83 const decoded = p.decode(Message, encoded); 84 assertEquals(decoded, { text }); 85 } 86}); 87 88Deno.test('int32 encoding/decoding', () => { 89 const Message = p.message({ value: p.int32() }, { value: 1 }); 90 91 const cases = [ 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 }, 144 ]; 145 146 for (const { value, expected } of cases) { 147 const encoded = p.encode(Message, { value }); 148 if (expected !== null) { 149 assertEquals(encoded, expected); 150 } 151 152 const decoded = p.decode(Message, encoded); 153 assertEquals(decoded, { value }); 154 } 155}); 156 157Deno.test('int64 encoding/decoding', () => { 158 const Message = p.message({ value: p.int64() }, { value: 1 }); 159 160 const cases = [ 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 }, 189 ]; 190 191 for (const { value, expected } of cases) { 192 const encoded = p.encode(Message, { value }); 193 if (expected !== null) { 194 assertEquals(encoded, expected); 195 } 196 197 const decoded = p.decode(Message, encoded); 198 assertEquals(decoded, { value }); 199 } 200}); 201 202Deno.test('uint32 encoding/decoding', () => { 203 const Message = p.message({ value: p.uint32() }, { value: 1 }); 204 205 const cases = [ 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 214 ]; 215 216 for (const { value, expected } of cases) { 217 const encoded = p.encode(Message, { value }); 218 if (expected !== null) { 219 assertEquals(encoded, expected); 220 } 221 222 const decoded = p.decode(Message, encoded); 223 assertEquals(decoded, { value }); 224 } 225}); 226 227Deno.test('uint64 encoding/decoding', () => { 228 const Message = p.message({ value: p.uint64() }, { value: 1 }); 229 230 const cases = [ 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 236 ]; 237 238 for (const { value, expected } of cases) { 239 const encoded = p.encode(Message, { value }); 240 if (expected !== null) { 241 assertEquals(encoded, expected); 242 } 243 244 const decoded = p.decode(Message, encoded); 245 assertEquals(decoded, { value }); 246 } 247}); 248 249Deno.test('sint32 encoding/decoding (zigzag)', () => { 250 const schema = p.message({ value: p.sint32() }, { value: 1 }); 251 252 const testCases = [ 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 262 ]; 263 264 for (const { value, expected } of testCases) { 265 const encoded = p.encode(schema, { value }); 266 if (expected !== null) { 267 assertEquals(encoded, expected); 268 } 269 270 const decoded = p.decode(schema, encoded); 271 assertEquals(decoded, { value }); 272 } 273}); 274 275Deno.test('sint64 encoding/decoding (zigzag)', () => { 276 const Message = p.message({ value: p.sint64() }, { value: 1 }); 277 278 const cases = [ 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 288 ]; 289 290 for (const { value, expected } of cases) { 291 const encoded = p.encode(Message, { value }); 292 if (expected !== null) { 293 assertEquals(encoded, expected); 294 } 295 296 const decoded = p.decode(Message, encoded); 297 assertEquals(decoded, { value }); 298 } 299}); 300 301Deno.test('float encoding/decoding', () => { 302 const Message = p.message({ value: p.float() }, { value: 1 }); 303 304 const cases = [ 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 }, 317 ]; 318 319 for (const { value, expected } of cases) { 320 const encoded = p.encode(Message, { value }); 321 if (expected !== null) { 322 assertEquals(encoded, expected); 323 } 324 325 const decoded = p.decode(Message, encoded); 326 327 // Special handling for infinity values 328 if (Number.isFinite(value)) { 329 // For finite values, check they're close due to float precision 330 // Use a more lenient tolerance for large numbers 331 const tolerance = Math.abs(value) > 1e9 ? Math.abs(value) * 1e-6 : 1e-6; 332 333 assertAlmostEquals(decoded.value, value, tolerance); 334 } else { 335 assertEquals(decoded.value, value); 336 } 337 } 338}); 339 340Deno.test('double encoding/decoding', () => { 341 const Message = p.message({ value: p.double() }, { value: 1 }); 342 343 const cases = [ 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 }, 384 ]; 385 386 for (const { value, expected } of cases) { 387 const encoded = p.encode(Message, { value }); 388 if (expected !== null) { 389 assertEquals(encoded, expected); 390 } 391 392 const decoded = p.decode(Message, encoded); 393 assertEquals(decoded, { value }); 394 } 395}); 396 397Deno.test('float range validation', () => { 398 const Message = p.message({ value: p.float() }, { value: 1 }); 399 400 // Test values that should cause range errors 401 const invald = [ 402 3.4028236e38, // slightly above max float32 403 -3.4028236e38, // slightly below min float32 404 1e39, // way above max float32 405 -1e39, // way below min float32 406 ]; 407 408 for (const value of invald) { 409 assertThrows(() => p.encode(Message, { value }), Error, 'invalid_range'); 410 } 411 412 // Test edge values that should work 413 const valid = [ 414 3.4028235e38, // max float32 415 -3.4028235e38, // min float32 416 1.175494e-38, // min positive float32 417 -1.175494e-38, // max negative float32 418 0, // zero 419 Infinity, // positive infinity 420 -Infinity, // negative infinity 421 NaN, // not a number 422 ]; 423 424 for (const value of valid) { 425 const encoded = p.encode(Message, { value }); 426 void p.decode(Message, encoded); 427 } 428}); 429 430Deno.test('double range validation', () => { 431 const Message = p.message({ value: p.double() }, { value: 1 }); 432 433 // JavaScript's number type is already IEEE 754 double precision, 434 // so all numbers are valid for double. Test edge cases work correctly. 435 const cases = [ 436 Number.MAX_VALUE, // max double 437 -Number.MAX_VALUE, // min double 438 Number.MIN_VALUE, // min positive double 439 -Number.MIN_VALUE, // max negative double 440 0, // zero 441 Infinity, // positive infinity 442 -Infinity, // negative infinity 443 NaN, // not a number 444 ]; 445 446 for (const value of cases) { 447 const encoded = p.encode(Message, { value }); 448 void p.decode(Message, encoded); 449 } 450}); 451 452Deno.test('boolean encoding/decoding', () => { 453 const Message = p.message({ value: p.boolean() }, { value: 1 }); 454 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 ]; 459 460 for (const { value, expected } of cases) { 461 const encoded = p.encode(Message, { value }); 462 assertEquals(encoded, expected); 463 464 const decoded = p.decode(Message, encoded); 465 assertEquals(decoded, { value }); 466 } 467}); 468 469Deno.test('bytes encoding/decoding', () => { 470 const Message = p.message({ data: p.bytes() }, { data: 1 }); 471 472 const cases = [ 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 }, 493 ]; 494 495 for (const { data, expected } of cases) { 496 const encoded = p.encode(Message, { data }); 497 if (expected !== null) { 498 assertEquals(encoded, expected); 499 } 500 501 const decoded = p.decode(Message, encoded); 502 assertEquals(decoded, { data }); 503 } 504}); 505 506Deno.test('fixed32 encoding/decoding', () => { 507 const Message = p.message({ value: p.fixed32() }, { value: 1 }); 508 509 const cases = [ 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 }, 530 ]; 531 532 for (const { value, expected } of cases) { 533 const encoded = p.encode(Message, { value }); 534 assertEquals(encoded, expected); 535 536 const decoded = p.decode(Message, encoded); 537 assertEquals(decoded, { value }); 538 } 539}); 540 541Deno.test('fixed64 encoding/decoding', () => { 542 const Message = p.message({ value: p.fixed64() }, { value: 1 }); 543 544 const cases = [ 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 }, 565 ]; 566 567 for (const { value, expected } of cases) { 568 const encoded = p.encode(Message, { value }); 569 assertEquals(encoded, expected); 570 571 const decoded = p.decode(Message, encoded); 572 assertEquals(decoded, { value }); 573 } 574}); 575 576Deno.test('sfixed32 encoding/decoding', () => { 577 const Message = p.message({ value: p.sfixed32() }, { value: 1 }); 578 579 const cases = [ 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) 585 ]; 586 587 for (const { value, expected } of cases) { 588 const encoded = p.encode(Message, { value }); 589 assertEquals(encoded, expected); 590 591 const decoded = p.decode(Message, encoded); 592 assertEquals(decoded, { value }); 593 } 594}); 595 596Deno.test('sfixed64 encoding/decoding', () => { 597 const Message = p.message({ value: p.sfixed64() }, { value: 1 }); 598 599 const cases = [ 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 }, 620 ]; 621 622 for (const { value, expected } of cases) { 623 const encoded = p.encode(Message, { value }); 624 assertEquals(encoded, expected); 625 626 const decoded = p.decode(Message, encoded); 627 assertEquals(decoded, { value }); 628 } 629}); 630 631// #endregion 632 633// #region Complex types 634 635Deno.test('repeated fields', () => { 636 const cases = [ 637 { 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 641 }, 642 { 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") 646 }, 647 { 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") 651 }, 652 { 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 659 }, 660 ]; 661 662 // Test non-packed repeated fields 663 { 664 const Message = p.message({ 665 numbers: p.repeated(p.int32(), false), 666 strings: p.repeated(p.string(), false), 667 }, { 668 numbers: 1, 669 strings: 2, 670 }); 671 672 for (const { data, unpacked } of cases) { 673 const encoded = p.encode(Message, data); 674 if (unpacked !== null) { 675 assertEquals(encoded, unpacked); 676 } 677 678 const decoded = p.decode(Message, encoded); 679 assertEquals(decoded, data); 680 } 681 } 682 683 // Test packed repeated fields (different wire format) 684 { 685 const Message = p.message({ 686 numbers: p.repeated(p.int32(), true), 687 strings: p.repeated(p.string(), true), 688 }, { 689 numbers: 1, 690 strings: 2, 691 }); 692 693 for (const { data, packed } of cases) { 694 const encoded = p.encode(Message, data); 695 if (packed !== null) { 696 assertEquals(encoded, packed); 697 } 698 699 const decoded = p.decode(Message, encoded); 700 assertEquals(decoded, data); 701 } 702 } 703}); 704 705Deno.test('messages with optional fields', () => { 706 const Message = p.message({ 707 required: p.string(), 708 withDefault: p.optional(p.string(), 'default_value'), 709 withFunctionDefault: p.optional(p.int32(), () => 42), 710 withoutDefault: p.optional(p.string()), 711 }, { 712 required: 1, 713 withDefault: 2, 714 withFunctionDefault: 3, 715 withoutDefault: 4, 716 }); 717 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 ]; 789 790 for (const { data, expected } of cases) { 791 const encoded = p.encode(Message, data); 792 assertEquals(encoded, expected); 793 794 const decoded = p.decode(Message, encoded); 795 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 } 814 } 815}); 816 817Deno.test('empty messages', () => { 818 const Message = p.message({}, {}); 819 820 const encoded = p.encode(Message, {}); 821 const decoded = p.decode(Message, encoded); 822 823 // Empty message should encode to empty buffer 824 assertEquals(encoded, new Uint8Array(0)); 825 assertEquals(decoded, {}); 826}); 827 828Deno.test('nested messages', () => { 829 { 830 const Simple = p.message({ 831 inner: p.optional(p.message({ value: p.int32() }, { value: 1 })), 832 }, { inner: 2 }); 833 834 const cases = [ 835 { 836 data: { inner: { value: 42 } }, 837 expected: Uint8Array.from([0x12, 0x02, 0x08, 0x2a]), // field 2: length 2, field 1: varint 42 838 }, 839 { 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', 884 }, 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 }; 897 898 const encoded = p.encode(Person, data); 899 const decoded = p.decode(Person, encoded); 900 901 assertEquals(decoded, data); 902 } 903}); 904 905Deno.test('self-referential messages', () => { 906 const Node = p.message({ 907 value: p.int32(), 908 get next() { 909 return p.optional(Node); 910 }, 911 }, { 912 value: 1, 913 next: 2, 914 }); 915 916 { 917 const encoded = p.encode(Node, { value: 42 }); 918 const decoded = p.decode(Node, encoded); 919 920 assertEquals(decoded, { value: 42 }); 921 } 922 923 { 924 const encoded = p.encode(Node, { value: 1, next: { value: 2 } }); 925 const decoded = p.decode(Node, encoded); 926 927 assertEquals(decoded, { value: 1, next: { value: 2 } }); 928 } 929 930 { 931 const complex: p.InferInput<typeof Node> = { 932 value: 1, 933 next: { 934 value: 2, 935 next: { 936 value: 3, 937 next: { 938 value: 4, 939 next: { 940 value: 5, 941 }, 942 }, 943 }, 944 }, 945 }; 946 947 const encoded = p.encode(Node, complex); 948 const decoded = p.decode(Node, encoded); 949 950 assertEquals(decoded, complex); 951 } 952}); 953 954Deno.test('map type', () => { 955 const Person = p.message({ 956 id: p.int32(), 957 name: p.string(), 958 }, { 959 id: 1, 960 name: 2, 961 }); 962 963 const PersonMap = p.map(p.string(), Person); 964 type PersonMap = p.InferInput<typeof PersonMap>; 965 966 const Message = p.message({ 967 map: PersonMap, 968 }, { 969 map: 1, 970 }); 971 972 { 973 const map: PersonMap = []; 974 975 const encoded = p.encode(Message, { map }); 976 const decoded = p.decode(Message, encoded); 977 978 assertEquals(decoded, { map }); 979 } 980 981 { 982 const map: PersonMap = [{ key: 'item1', value: { id: 1, name: 'first' } }]; 983 984 const encoded = p.encode(Message, { map }); 985 const decoded = p.decode(Message, encoded); 986 987 assertEquals(decoded, { map }); 988 } 989 990 { 991 const map: PersonMap = [ 992 { key: 'item1', value: { id: 1, name: 'first' } }, 993 { key: 'item2', value: { id: 2, name: 'second' } }, 994 ]; 995 996 const encoded = p.encode(Message, { map }); 997 const decoded = p.decode(Message, encoded); 998 999 assertEquals(decoded, { map }); 1000 } 1001}); 1002 1003Deno.test('Timestamp type', () => { 1004 { 1005 const encoded = p.encode(p.Timestamp, {}); 1006 const decoded = p.decode(p.Timestamp, encoded); 1007 1008 assertEquals(decoded, { seconds: 0n, nanos: 0 }); 1009 } 1010 1011 { 1012 const data: p.InferInput<typeof p.Timestamp> = { 1013 seconds: 1609459200n, // 2021-01-01 00:00:00 UTC 1014 nanos: 123456789, 1015 }; 1016 1017 const encoded = p.encode(p.Timestamp, data); 1018 const decoded = p.decode(p.Timestamp, encoded); 1019 1020 assertEquals(decoded, data); 1021 } 1022}); 1023 1024Deno.test('Duration type', () => { 1025 { 1026 const data: p.InferInput<typeof p.Duration> = { 1027 seconds: 3661n, // 1 hour, 1 minute, 1 second 1028 nanos: 500000000, // 0.5 seconds 1029 }; 1030 1031 const encoded = p.encode(p.Duration, data); 1032 const decoded = p.decode(p.Duration, encoded); 1033 1034 assertEquals(decoded, data); 1035 } 1036 1037 { 1038 const negative: p.InferInput<typeof p.Duration> = { 1039 seconds: -30n, 1040 nanos: -500000000, 1041 }; 1042 1043 const encoded = p.encode(p.Duration, negative); 1044 const decoded = p.decode(p.Duration, encoded); 1045 1046 assertEquals(decoded, negative); 1047 } 1048}); 1049 1050Deno.test('Any type', () => { 1051 { 1052 const messageSchema = p.message({ value: p.string() }, { value: 1 }); 1053 1054 const data = { 1055 typeUrl: 'type.googleapis.com/test.Message', 1056 value: p.encode(messageSchema, { value: 'hello world' }), 1057 }; 1058 1059 const encoded = p.encode(p.Any, data); 1060 const decoded = p.decode(p.Any, encoded); 1061 1062 assertEquals(decoded, data); 1063 } 1064 1065 { 1066 const encoded = p.encode(p.Any, {}); 1067 const decoded = p.decode(p.Any, encoded); 1068 1069 assertEquals(decoded, { typeUrl: '', value: new Uint8Array(0) }); 1070 } 1071}); 1072 1073// #endregion 1074 1075// #region Edge cases 1076 1077Deno.test('large varint values', () => { 1078 const Message = p.message({ value: p.int32() }, { value: 1 }); 1079 1080 // Test values that require multiple bytes in varint encoding 1081 const cases = [ 1082 127, // 1 byte 1083 128, // 2 bytes 1084 16383, // 2 bytes 1085 16384, // 3 bytes 1086 2097151, // 3 bytes 1087 2097152, // 4 bytes 1088 ]; 1089 1090 for (const value of cases) { 1091 const encoded = p.encode(Message, { value }); 1092 const decoded = p.decode(Message, encoded); 1093 1094 assertEquals(decoded, { value }); 1095 } 1096}); 1097 1098Deno.test('very large varint handling', () => { 1099 const schema = p.message({ value: p.int32() }, { value: 1 }); 1100 1101 // Create a very large varint that would overflow 32-bit int 1102 // This represents 0xFFFFFFFF (4294967295) which should become -1 when cast to int32 1103 const largeVarintData = Uint8Array.from([ 1104 8, 1105 0xFF, 1106 0xFF, 1107 0xFF, 1108 0xFF, 1109 0x0F, // field 1: max uint32 value 1110 ]); 1111 1112 const decoded = p.decode(schema, largeVarintData); 1113 assertEquals(decoded, { value: -1 }); // Should wrap around to -1 1114}); 1115 1116Deno.test('unknown field handling', () => { 1117 const Message = p.message({ known: p.string() }, { known: 1 }); 1118 1119 const buffer = Uint8Array.from([ 1120 10, 1121 5, 1122 72, 1123 101, 1124 108, 1125 108, 1126 111, // field 1: "Hello" 1127 18, 1128 4, 1129 116, 1130 101, 1131 115, 1132 116, // field 2: "test" (unknown) 1133 26, 1134 3, 1135 98, 1136 121, 1137 101, // field 3: "bye" (unknown) 1138 ]); 1139 1140 const decoded = p.decode(Message, buffer); 1141 1142 assertEquals(decoded, { known: 'Hello' }); 1143}); 1144 1145Deno.test('duplicate field handling', () => { 1146 const Message = p.message({ value: p.int32() }, { value: 1 }); 1147 1148 const buffer = Uint8Array.from([ 1149 8, 1150 42, // field 1: value 42 1151 8, 1152 24, // field 1: value 24 (duplicate) 1153 ]); 1154 1155 const decoded = p.decode(Message, buffer); 1156 1157 assertEquals(decoded, { value: 24 }); 1158}); 1159 1160// #endregion 1161 1162// #region Input validation errors 1163 1164Deno.test('type validation errors during encoding', () => { 1165 const StringMessage = p.message({ text: p.string() }, { text: 1 }); 1166 { 1167 // @ts-expect-error: purposeful type error 1168 const result = p.tryEncode(StringMessage, 123); 1169 1170 assert(!result.ok); 1171 assertEquals(result.message, `invalid_type at . (expected object)`); 1172 } 1173 1174 { 1175 // @ts-expect-error: purposeful type error 1176 const result = p.tryEncode(StringMessage, { text: 123 }); 1177 1178 assert(!result.ok); 1179 assertEquals(result.message, `invalid_type at .text (expected string)`); 1180 } 1181 1182 const Int64Message = p.message({ value: p.int64() }, { value: 1 }); 1183 { 1184 // @ts-expect-error: purposeful type error 1185 const result = p.tryEncode(Int64Message, { value: 123 }); 1186 1187 assert(!result.ok); 1188 assertEquals(result.message, `invalid_type at .value (expected bigint)`); 1189 } 1190 1191 const Uint64Message = p.message({ value: p.uint64() }, { value: 1 }); 1192 { 1193 // @ts-expect-error: purposeful type error 1194 const result = p.tryEncode(Uint64Message, { value: 123 }); 1195 1196 assert(!result.ok); 1197 assertEquals(result.message, `invalid_type at .value (expected bigint)`); 1198 } 1199}); 1200 1201Deno.test('range validation for unsigned types', () => { 1202 const Uint32Message = p.message({ value: p.uint32() }, { value: 1 }); 1203 const Uint64Message = p.message({ value: p.uint64() }, { value: 1 }); 1204 const Fixed32Message = p.message({ value: p.fixed32() }, { value: 1 }); 1205 const Fixed64Message = p.message({ value: p.fixed64() }, { value: 1 }); 1206 1207 // uint32 range errors 1208 assertThrows(() => p.encode(Uint32Message, { value: -1 }), p.ProtobufError); 1209 assertThrows(() => p.encode(Uint32Message, { value: 4294967296 }), p.ProtobufError); 1210 1211 // uint64 range errors 1212 assertThrows(() => p.encode(Uint64Message, { value: -1n }), p.ProtobufError); 1213 1214 // fixed32 range errors 1215 assertThrows(() => p.encode(Fixed32Message, { value: -1 }), p.ProtobufError); 1216 assertThrows(() => p.encode(Fixed32Message, { value: 4294967296 }), p.ProtobufError); 1217 1218 // fixed64 range errors 1219 assertThrows(() => p.encode(Fixed64Message, { value: -1n }), p.ProtobufError); 1220}); 1221 1222// #endregion 1223 1224// #region Decoding validation errors 1225 1226Deno.test('invalid wire type handling', () => { 1227 const Message = p.message({ text: p.string() }, { text: 1 }); 1228 1229 // Manually create invalid wire data: field 1 with wire type 0 (varint) instead of 2 (length-delimited) 1230 const invalidData = Uint8Array.from([ 1231 8, 1232 72, // field 1, wire type 0, value 72 1233 ]); 1234 1235 assertThrows(() => p.decode(Message, invalidData), p.ProtobufError); 1236 1237 const result = p.tryDecode(Message, invalidData); 1238 assert(!result.ok); 1239 1240 assertEquals(result.issues.length, 1); 1241 const issue = result.issues[0]; 1242 1243 assertEquals(issue.code, 'invalid_wire'); 1244 assertEquals(issue.path, ['text']); 1245 1246 if (issue.code === 'invalid_wire') { 1247 assertEquals(issue.expected, 2); // string expects wire type 2 1248 } 1249 1250 assertStringIncludes(result.message, 'invalid_wire at'); 1251 assertStringIncludes(result.message, 'expected wire type'); 1252 assertStringIncludes(result.message, 'text'); 1253}); 1254 1255Deno.test('multiple validation errors', () => { 1256 const Message = p.message({ 1257 field1: p.string(), 1258 field2: p.int32(), 1259 field3: p.bytes(), 1260 }, { 1261 field1: 1, 1262 field2: 2, 1263 field3: 3, 1264 }); 1265 1266 // Create data with wrong wire types for multiple fields 1267 const invalidData = Uint8Array.from([ 1268 8, 1269 72, // field 1, wire type 0 instead of 2 1270 18, 1271 1, 1272 65, // field 2, wire type 2 instead of 0 1273 24, 1274 100, // field 3, wire type 0 instead of 2 1275 ]); 1276 1277 const result = p.tryDecode(Message, invalidData); 1278 assert(!result.ok); 1279 1280 assertEquals(result.message, 'invalid_wire at .field1 (expected wire type 2)'); 1281}); 1282 1283Deno.test('missing required fields during decoding', () => { 1284 const Message = p.message({ 1285 required1: p.string(), 1286 optional: p.optional(p.string()), 1287 }, { 1288 required1: 1, 1289 required2: 2, 1290 optional: 3, 1291 }); 1292 1293 // Create a message missing required fields 1294 const partialSchema = p.message({ 1295 optional: p.optional(p.string()), 1296 }, { 1297 optional: 3, 1298 }); 1299 1300 const encoded = p.encode(partialSchema, { optional: 'hello' }); 1301 1302 const result = p.tryDecode(Message, encoded); 1303 assert(!result.ok); 1304 assertEquals(result.message, 'missing_value at .required1 (required field is missing)'); 1305}); 1306 1307Deno.test('empty buffer handling', () => { 1308 const Message = p.message({ 1309 required: p.string(), 1310 optional: p.optional(p.string()), 1311 }, { 1312 required: 1, 1313 optional: 2, 1314 }); 1315 1316 const emptyBuffer = new Uint8Array(0); 1317 1318 const result = p.tryDecode(Message, emptyBuffer); 1319 assert(!result.ok); 1320 1321 assertEquals(result.issues.length, 1); 1322 assertEquals(result.issues[0].code, 'missing_value'); 1323 assertEquals(result.issues[0].path, ['required']); 1324}); 1325 1326// #endregion 1327 1328// #region Buffer corruption and underrun 1329 1330Deno.test('corrupted data handling', () => { 1331 const Message = p.message({ text: p.string() }, { text: 1 }); 1332 1333 // Test with truncated data (incomplete varint) 1334 const truncatedData = Uint8Array.from([10, 5, 72, 101]); // says length 5 but only has 2 bytes 1335 1336 assertThrows(() => p.decode(Message, truncatedData)); 1337}); 1338 1339Deno.test('buffer underrun during decoding', () => { 1340 const Message = p.message({ text: p.string() }, { text: 1 }); 1341 1342 // Create a truncated buffer that claims to have more data than it actually has 1343 // Wire format: tag (8=field 1, wire type 2) + length (5) + partial data (only 2 bytes instead of 5) 1344 const truncatedBuffer = Uint8Array.from([ 1345 8 | 2, // tag: field 1, wire type 2 (length-delimited) 1346 5, // length: claims 5 bytes follow 1347 65, 1348 66, // only 2 bytes: "AB" 1349 // missing 3 bytes! 1350 ]); 1351 1352 const result = p.tryDecode(Message, truncatedBuffer); 1353 1354 assert(!result.ok); 1355 assertEquals(result.message, `unexpected_eof at .text (unexpected end of input)`); 1356}); 1357 1358Deno.test('buffer underrun during varint reading', () => { 1359 const Message = p.message({ value: p.int32() }, { value: 1 }); 1360 1361 // Create a buffer with incomplete varint (continuation bit set but no more bytes) 1362 const incompleteVarint = Uint8Array.from([ 1363 8, // tag: field 1, wire type 0 (varint) 1364 0x80, // varint with continuation bit set, but no more bytes 1365 ]); 1366 1367 const result = p.tryDecode(Message, incompleteVarint); 1368 1369 assert(!result.ok); 1370 assertEquals(result.message, `unexpected_eof at .value (unexpected end of input)`); 1371}); 1372 1373// #endregion 1374 1375// #region Performance tests 1376 1377Deno.test('round-trip consistency stress test', () => { 1378 const Message = p.message({ 1379 id: p.int64(), 1380 name: p.string(), 1381 email: p.optional(p.string()), 1382 tags: p.repeated(p.string()), 1383 metadata: p.bytes(), 1384 score: p.double(), 1385 active: p.boolean(), 1386 }, { 1387 id: 1, 1388 name: 2, 1389 email: 3, 1390 tags: 4, 1391 metadata: 5, 1392 score: 6, 1393 active: 7, 1394 }); 1395 1396 // Generate random test data 1397 for (let i = 0; i < 100; i++) { 1398 const data = { 1399 id: BigInt(Math.floor(Math.random() * 1000000)), 1400 name: `user_${i}`, 1401 email: Math.random() > 0.5 ? `user_${i}@example.com` : undefined, 1402 tags: Array.from({ length: Math.floor(Math.random() * 5) }, (_, j) => `tag_${j}`), 1403 metadata: new Uint8Array( 1404 Array.from({ length: Math.floor(Math.random() * 20) }, () => Math.floor(Math.random() * 255)), 1405 ), 1406 score: Math.random() * 100, 1407 active: Math.random() > 0.5, 1408 }; 1409 1410 const encoded = p.encode(Message, data); 1411 const decoded = p.decode(Message, encoded); 1412 1413 // `assertEquals` throws if properties aren't present entirely. 1414 decoded.email ??= undefined; 1415 1416 assertEquals(decoded, data, `Failed for iteration ${i}`); 1417 } 1418}); 1419 1420Deno.test('very large arrays', () => { 1421 const strings = Array.from({ length: 10000 }, () => nanoid(8)); 1422 1423 { 1424 const Message = p.message({ strings: p.repeated(p.string(), false) }, { strings: 1 }); 1425 1426 const encoded = p.encode(Message, { strings }); 1427 const decoded = p.decode(Message, encoded); 1428 1429 assertEquals(decoded, { strings }); 1430 } 1431 1432 { 1433 const Message = p.message({ strings: p.repeated(p.string(), true) }, { strings: 1 }); 1434 1435 const encoded = p.encode(Message, { strings }); 1436 const decoded = p.decode(Message, encoded); 1437 1438 assertEquals(decoded, { strings }); 1439 } 1440}); 1441 1442Deno.test('large string handling', () => { 1443 const Message = p.message({ text: p.string() }, { text: 1 }); 1444 1445 const text = nanoid(1048576); 1446 1447 const encoded = p.encode(Message, { text }); 1448 const decoded = p.decode(Message, encoded); 1449 1450 assertEquals(decoded, { text }); 1451}); 1452 1453Deno.test('large byte array handling', () => { 1454 const Message = p.message({ data: p.bytes() }, { data: 1 }); 1455 1456 const data = new Uint8Array(1024 * 1024); 1457 for (let i = 0; i < data.length; i++) { 1458 data[i] = Math.floor(Math.random() * 255); 1459 } 1460 1461 const encoded = p.encode(Message, { data }); 1462 const decoded = p.decode(Message, encoded); 1463 1464 assertEquals(decoded, { data }); 1465}); 1466 1467// #endregion