+4
-21
lib/mod.test.ts
+4
-21
lib/mod.test.ts
···
941
941
const result = p.tryDecode(Message, invalidData);
942
942
assert(!result.ok);
943
943
944
-
assertEquals(result.issues.length, 3); // field1, field2, and field3 have wrong wire types
945
-
946
-
const codes = result.issues.map((issue) => issue.code);
947
-
assert(codes.every((code) => code === 'invalid_wire'));
948
-
949
-
const fields = result.issues.map((issue) => issue.path[0]);
950
-
assertArrayIncludes(fields, ['field1', 'field2', 'field3']);
951
-
952
-
assertStringIncludes(result.message, '+2 other issue(s)');
944
+
assertEquals(result.message, 'invalid_wire at .field1 (expected wire type 2)');
953
945
});
954
946
955
947
Deno.test('missing required fields during decoding', () => {
956
948
const Message = p.message({
957
949
required1: p.string(),
958
-
required2: p.int32(),
959
950
optional: p.optional(p.string()),
960
951
}, {
961
952
required1: 1,
···
974
965
975
966
const result = p.tryDecode(Message, encoded);
976
967
assert(!result.ok);
977
-
978
-
const issues = result.issues;
979
-
assertEquals(issues.length, 2);
980
-
981
-
const missingCodes = issues.map((issue) => issue.code);
982
-
assertArrayIncludes(missingCodes, ['missing_value', 'missing_value']);
983
-
984
-
const missingKeys = issues.map((issue) => issue.path.join('.'));
985
-
assertArrayIncludes(missingKeys, ['required1', 'required2']);
968
+
assertEquals(result.message, 'missing_value at .required1 (required field is missing)');
986
969
});
987
970
988
971
Deno.test('empty buffer handling', () => {
···
1033
1016
const result = p.tryDecode(Message, truncatedBuffer);
1034
1017
1035
1018
assert(!result.ok);
1036
-
assertEquals(result.message, `unexpected_eof at .text (unexpected end of input) (+1 other issue(s))`);
1019
+
assertEquals(result.message, `unexpected_eof at .text (unexpected end of input)`);
1037
1020
});
1038
1021
1039
1022
Deno.test('buffer underrun during varint reading', () => {
···
1048
1031
const result = p.tryDecode(Message, incompleteVarint);
1049
1032
1050
1033
assert(!result.ok);
1051
-
assertEquals(result.message, `unexpected_eof at .value (unexpected end of input) (+1 other issue(s))`);
1034
+
assertEquals(result.message, `unexpected_eof at .value (unexpected end of input)`);
1052
1035
});
1053
1036
1054
1037
// #endregion
+39
-124
lib/mod.ts
+39
-124
lib/mod.ts
···
39
39
40
40
type IssueTree =
41
41
| IssueLeaf
42
-
| { ok: false; code: 'prepend'; key: Key; tree: IssueTree }
43
-
| { ok: false; code: 'join'; left: IssueTree; right: IssueTree };
42
+
| { ok: false; code: 'prepend'; key: Key; tree: IssueTree };
44
43
45
44
export type Issue = Readonly<
46
45
| { code: 'unexpected_eof'; path: Key[] }
···
68
67
const UINT64_RANGE_ISSUE: IssueLeaf = { ok: false, code: 'invalid_range', type: 'uint64' };
69
68
70
69
// #__NO_SIDE_EFFECTS__
71
-
const joinIssues = (left: IssueTree | undefined, right: IssueTree): IssueTree => {
72
-
return left ? { ok: false, code: 'join', left, right } : right;
73
-
};
74
-
75
-
// #__NO_SIDE_EFFECTS__
76
70
const prependPath = (key: Key, tree: IssueTree): IssueTree => {
77
71
return { ok: false, code: 'prepend', key, tree };
78
72
};
···
88
82
const collectIssues = (tree: IssueTree, path: Key[] = [], issues: Issue[] = []): Issue[] => {
89
83
for (;;) {
90
84
switch (tree.code) {
91
-
case 'join': {
92
-
collectIssues(tree.left, path.slice(), issues);
93
-
tree = tree.right;
94
-
continue;
95
-
}
96
85
case 'prepend': {
97
86
path.push(tree.key);
98
87
tree = tree.tree;
···
106
95
}
107
96
};
108
97
109
-
const countIssues = (tree: IssueTree): number => {
110
-
let count = 0;
111
-
for (;;) {
112
-
switch (tree.code) {
113
-
case 'join': {
114
-
count += countIssues(tree.left);
115
-
tree = tree.right;
116
-
continue;
117
-
}
118
-
case 'prepend': {
119
-
tree = tree.tree;
120
-
continue;
121
-
}
122
-
default: {
123
-
return count + 1;
124
-
}
125
-
}
126
-
}
127
-
};
128
-
129
98
const formatIssueTree = (tree: IssueTree): string => {
130
99
let path = '';
131
-
let count = 0;
132
100
for (;;) {
133
101
switch (tree.code) {
134
-
case 'join': {
135
-
count += countIssues(tree.right);
136
-
tree = tree.left;
137
-
continue;
138
-
}
139
102
case 'prepend': {
140
103
path += `.${tree.key}`;
141
104
tree = tree.tree;
···
168
131
break;
169
132
}
170
133
171
-
let msg = `${tree.code} at ${path || '.'} (${message})`;
172
-
if (count > 0) {
173
-
msg += ` (+${count} other issue(s))`;
174
-
}
175
-
176
-
return msg;
134
+
return `${tree.code} at ${path || '.'} (${message})`;
177
135
};
178
136
179
137
// #endregion
···
266
224
): Result<InferOutput<TSchema>> => {
267
225
const state = createDecoderState(buffer);
268
226
269
-
const result = schema['~~decode'](state, FLAG_EMPTY);
227
+
const result = schema['~~decode'](state);
270
228
271
229
if (result.ok) {
272
230
return result as Ok<InferOutput<TSchema>>;
···
310
268
): InferOutput<TSchema> => {
311
269
const state = createDecoderState(buffer);
312
270
313
-
const result = schema['~~decode'](state, FLAG_EMPTY);
271
+
const result = schema['~~decode'](state);
314
272
315
273
if (result.ok) {
316
274
return result.value as InferOutput<TSchema>;
···
541
499
declare const kObjectType: unique symbol;
542
500
type kObjectType = typeof kObjectType;
543
501
544
-
// None set
545
-
export const FLAG_EMPTY = 0;
546
-
// Don't continue validation if an error is encountered
547
-
export const FLAG_ABORT_EARLY = 1 << 0;
548
-
549
502
type RawResult<T = unknown> = Ok<T> | IssueTree;
550
503
551
-
type Decoder = (this: void, state: DecoderState, flags: number) => RawResult;
504
+
type Decoder = (this: void, state: DecoderState) => RawResult;
552
505
553
506
type Encoder = (this: void, state: EncoderState, input: unknown) => IssueTree | void;
554
507
···
580
533
kind: 'schema',
581
534
type: 'string',
582
535
wire: 2,
583
-
'~decode'(state, _flags) {
536
+
'~decode'(state) {
584
537
const length = readVarint(state);
585
538
if (!length.ok) {
586
539
return length;
···
642
595
kind: 'schema',
643
596
type: 'bytes',
644
597
wire: 2,
645
-
'~decode'(state, _flags) {
598
+
'~decode'(state) {
646
599
const length = readVarint(state);
647
600
if (!length.ok) {
648
601
return length;
···
681
634
kind: 'schema',
682
635
type: 'boolean',
683
636
wire: 0,
684
-
'~decode'(state, _flags) {
637
+
'~decode'(state) {
685
638
const result = readVarint(state);
686
639
if (!result.ok) {
687
640
return result;
···
718
671
kind: 'schema',
719
672
type: 'double',
720
673
wire: 1,
721
-
'~decode'(state, _flags) {
674
+
'~decode'(state) {
722
675
const view = getDataView(state);
723
676
const value = view.getFloat64(state.p, true);
724
677
···
759
712
kind: 'schema',
760
713
type: 'float',
761
714
wire: 5,
762
-
'~decode'(state, _flags) {
715
+
'~decode'(state) {
763
716
const view = getDataView(state);
764
717
const value = view.getFloat32(state.p, true);
765
718
···
807
760
kind: 'schema',
808
761
type: 'int32',
809
762
wire: 0,
810
-
'~decode'(state, _flags) {
763
+
'~decode'(state) {
811
764
const result = readVarint(state);
812
765
if (!result.ok) {
813
766
return result;
···
852
805
kind: 'schema',
853
806
type: 'int64',
854
807
wire: 0,
855
-
'~decode'(state, _flags) {
808
+
'~decode'(state) {
856
809
const buf = state.b;
857
810
let pos = state.p;
858
811
···
914
867
kind: 'schema',
915
868
type: 'uint32',
916
869
wire: 0,
917
-
'~decode'(state, _flags) {
870
+
'~decode'(state) {
918
871
const result = readVarint(state);
919
872
if (!result.ok) {
920
873
return result;
···
957
910
kind: 'schema',
958
911
type: 'uint64',
959
912
wire: 0,
960
-
'~decode'(state, _flags) {
913
+
'~decode'(state) {
961
914
const buf = state.b;
962
915
let pos = state.p;
963
916
···
1007
960
kind: 'schema',
1008
961
type: 'sint32',
1009
962
wire: 0,
1010
-
'~decode'(state, _flags) {
963
+
'~decode'(state) {
1011
964
const result = readVarint(state);
1012
965
if (!result.ok) {
1013
966
return result;
···
1050
1003
kind: 'schema',
1051
1004
type: 'sint64',
1052
1005
wire: 0,
1053
-
'~decode'(state, _flags) {
1006
+
'~decode'(state) {
1054
1007
const buf = state.b;
1055
1008
let pos = state.p;
1056
1009
···
1101
1054
kind: 'schema',
1102
1055
type: 'fixed32',
1103
1056
wire: 5,
1104
-
'~decode'(state, _flags) {
1057
+
'~decode'(state) {
1105
1058
const view = getDataView(state);
1106
1059
const value = view.getUint32(state.p, true);
1107
1060
···
1145
1098
kind: 'schema',
1146
1099
type: 'fixed64',
1147
1100
wire: 1,
1148
-
'~decode'(state, _flags) {
1101
+
'~decode'(state) {
1149
1102
const view = getDataView(state);
1150
1103
1151
1104
// Read as two 32-bit values and combine into a BigInt
···
1196
1149
kind: 'schema',
1197
1150
type: 'sfixed32',
1198
1151
wire: 5,
1199
-
'~decode'(state, _flags) {
1152
+
'~decode'(state) {
1200
1153
const view = getDataView(state);
1201
1154
const value = view.getInt32(state.p, true);
1202
1155
···
1240
1193
kind: 'schema',
1241
1194
type: 'sfixed64',
1242
1195
wire: 1,
1243
-
'~decode'(state, _flags) {
1196
+
'~decode'(state) {
1244
1197
const view = getDataView(state);
1245
1198
1246
1199
// Read as two 32-bit values and combine into a BigInt
···
1312
1265
get '~decode'() {
1313
1266
const shape = resolvedShape.value;
1314
1267
1315
-
const decoder: Decoder = (state, flags) => {
1268
+
const decoder: Decoder = (state) => {
1316
1269
const length = readVarint(state);
1317
1270
if (!length.ok) {
1318
1271
return length;
···
1335
1288
let issues: IssueTree | undefined;
1336
1289
1337
1290
while (children.p < length.value) {
1338
-
const r = shape['~decode'](children, flags);
1291
+
const r = shape['~decode'](children);
1339
1292
1340
-
if (r.ok) {
1341
-
array.push(r.value);
1342
-
} else {
1343
-
issues = joinIssues(issues, prependPath(idx, r));
1344
-
1345
-
if (flags & FLAG_ABORT_EARLY) {
1346
-
return issues;
1347
-
}
1293
+
if (!r.ok) {
1294
+
return prependPath(idx, r);
1348
1295
}
1349
1296
1297
+
array.push(r.value);
1350
1298
idx++;
1351
1299
}
1352
1300
···
1435
1383
wrapped: wrapped,
1436
1384
default: defaultValue,
1437
1385
wire: wrapped.wire,
1438
-
'~decode'(state, flags) {
1439
-
return wrapped['~decode'](state, flags);
1386
+
'~decode'(state) {
1387
+
return wrapped['~decode'](state);
1440
1388
},
1441
1389
'~encode'(state, input) {
1442
1390
return wrapped['~encode'](state, input);
···
1583
1531
const shape = resolvedEntries.value;
1584
1532
const len = Object.keys(shape).length;
1585
1533
1586
-
const decoder: Decoder = (state, flags) => {
1534
+
const decoder: Decoder = (state) => {
1587
1535
let seenBits: BitSet = 0;
1588
1536
let seenCount = 0;
1589
1537
···
1594
1542
while (state.p < end) {
1595
1543
const prelude = readVarint(state);
1596
1544
if (!prelude.ok) {
1597
-
issues = joinIssues(issues, prelude);
1598
-
if (flags & FLAG_ABORT_EARLY) {
1599
-
return issues;
1600
-
}
1601
-
1602
-
break;
1545
+
return prelude;
1603
1546
}
1604
1547
1605
1548
const magic = prelude.value;
···
1612
1555
if (!entry) {
1613
1556
const result = skipField(state, wire);
1614
1557
if (!result.ok) {
1615
-
issues = joinIssues(issues, result);
1616
-
if (flags & FLAG_ABORT_EARLY) {
1617
-
return issues;
1618
-
}
1619
-
1620
-
break;
1558
+
return result;
1621
1559
}
1560
+
1622
1561
continue;
1623
1562
}
1624
1563
···
1630
1569
1631
1570
// It doesn't match with our wire, file an issue
1632
1571
if (entry.wire !== wire) {
1633
-
issues = joinIssues(issues, entry.wireIssue);
1634
-
if (flags & FLAG_ABORT_EARLY) {
1635
-
return issues;
1636
-
}
1637
-
1638
-
const skip = skipField(state, wire);
1639
-
if (!skip.ok) {
1640
-
issues = joinIssues(issues, prependPath(entry.key, skip));
1641
-
if (flags & FLAG_ABORT_EARLY) {
1642
-
return issues;
1643
-
}
1644
-
1645
-
break;
1646
-
}
1647
-
1648
-
continue;
1572
+
return entry.wireIssue;
1649
1573
}
1650
1574
1651
1575
// Decode the value
1652
-
const result = entry.schema['~decode'](state, flags);
1576
+
const result = entry.schema['~decode'](state);
1653
1577
1654
1578
// Failed to decode, file an issue
1655
1579
if (!result.ok) {
1656
-
issues = joinIssues(issues, prependPath(entry.key, result));
1657
-
if (flags & FLAG_ABORT_EARLY) {
1658
-
return issues;
1659
-
}
1660
-
1661
-
continue;
1580
+
return prependPath(entry.key, result);
1662
1581
}
1663
1582
1664
1583
/*#__INLINE__*/ set(obj, entry.key, result.value);
···
1681
1600
/*#__INLINE__*/ set(obj, entry.key, defaultValue);
1682
1601
}
1683
1602
} else {
1684
-
issues = joinIssues(issues, entry.missingIssue);
1685
-
1686
-
if (flags & FLAG_ABORT_EARLY) {
1687
-
return issues;
1688
-
}
1603
+
return entry.missingIssue;
1689
1604
}
1690
1605
}
1691
1606
}
···
1703
1618
get '~decode'() {
1704
1619
const raw = this['~~decode'];
1705
1620
1706
-
const decoder: Decoder = (state, flags) => {
1621
+
const decoder: Decoder = (state) => {
1707
1622
const length = readVarint(state);
1708
1623
if (!length.ok) {
1709
1624
return length;
···
1720
1635
v: null,
1721
1636
};
1722
1637
1723
-
return raw(child, flags);
1638
+
return raw(child);
1724
1639
};
1725
1640
1726
1641
return lazyProperty(this, '~decode', decoder);
···
1834
1749
key,
1835
1750
value,
1836
1751
get '~decode'() {
1837
-
const decoder: Decoder = (state, flags) => {
1838
-
const result = Schema['~decode'](state, flags);
1752
+
const decoder: Decoder = (state) => {
1753
+
const result = Schema['~decode'](state);
1839
1754
if (!result.ok) {
1840
1755
return result;
1841
1756
}