@bcts/dcbor 1.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +48 -0
- package/README.md +13 -0
- package/dist/index.cjs +9151 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3107 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +3107 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +9155 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +9027 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +80 -0
- package/src/.claude-flow/metrics/agent-metrics.json +1 -0
- package/src/.claude-flow/metrics/performance.json +87 -0
- package/src/.claude-flow/metrics/task-metrics.json +10 -0
- package/src/byte-string.ts +300 -0
- package/src/cbor-codable.ts +170 -0
- package/src/cbor-tagged-codable.ts +72 -0
- package/src/cbor-tagged-decodable.ts +184 -0
- package/src/cbor-tagged-encodable.ts +138 -0
- package/src/cbor-tagged.ts +104 -0
- package/src/cbor.ts +869 -0
- package/src/conveniences.ts +840 -0
- package/src/date.ts +553 -0
- package/src/decode.ts +276 -0
- package/src/diag.ts +462 -0
- package/src/dump.ts +277 -0
- package/src/error.ts +259 -0
- package/src/exact.ts +714 -0
- package/src/float.ts +279 -0
- package/src/global.d.ts +34 -0
- package/src/globals.d.ts +0 -0
- package/src/index.ts +180 -0
- package/src/map.ts +308 -0
- package/src/prelude.ts +70 -0
- package/src/set.ts +515 -0
- package/src/simple.ts +153 -0
- package/src/stdlib.ts +55 -0
- package/src/string-util.ts +55 -0
- package/src/tag.ts +53 -0
- package/src/tags-store.ts +294 -0
- package/src/tags.ts +231 -0
- package/src/varint.ts +124 -0
- package/src/walk.ts +516 -0
package/src/dump.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hex dump utilities for CBOR data.
|
|
3
|
+
*
|
|
4
|
+
* Affordances for viewing the encoded binary representation of CBOR as hexadecimal.
|
|
5
|
+
* Optionally annotates the output, breaking it up into semantically meaningful lines,
|
|
6
|
+
* formatting dates, and adding names of known tags.
|
|
7
|
+
*
|
|
8
|
+
* @module dump
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { type Cbor, MajorType, cborData } from "./cbor";
|
|
12
|
+
import { encodeVarInt } from "./varint";
|
|
13
|
+
import { flanked, sanitized } from "./string-util";
|
|
14
|
+
import type { TagsStore } from "./tags-store";
|
|
15
|
+
import { getGlobalTagsStore } from "./tags-store";
|
|
16
|
+
import { createTag } from "./tag";
|
|
17
|
+
import { CborError } from "./error";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for hex formatting.
|
|
21
|
+
*/
|
|
22
|
+
export interface HexFormatOpts {
|
|
23
|
+
/** Whether to annotate the hex dump with semantic information */
|
|
24
|
+
annotate?: boolean;
|
|
25
|
+
/** Optional tags store for resolving tag names */
|
|
26
|
+
tagsStore?: TagsStore;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert bytes to hex string.
|
|
31
|
+
*/
|
|
32
|
+
export const bytesToHex = (bytes: Uint8Array): string => {
|
|
33
|
+
return Array.from(bytes)
|
|
34
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
35
|
+
.join("");
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert hex string to bytes.
|
|
40
|
+
*/
|
|
41
|
+
export const hexToBytes = (hexString: string): Uint8Array => {
|
|
42
|
+
const hex = hexString.replace(/\s/g, "");
|
|
43
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
44
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
45
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns the encoded hexadecimal representation of CBOR.
|
|
52
|
+
*
|
|
53
|
+
* @param cbor - CBOR value to convert
|
|
54
|
+
* @returns Hex string
|
|
55
|
+
*/
|
|
56
|
+
export const hex = (cbor: Cbor): string => bytesToHex(cborData(cbor));
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns the encoded hexadecimal representation of CBOR with options.
|
|
60
|
+
*
|
|
61
|
+
* Optionally annotates the output, e.g., breaking the output up into
|
|
62
|
+
* semantically meaningful lines, formatting dates, and adding names of
|
|
63
|
+
* known tags.
|
|
64
|
+
*
|
|
65
|
+
* @param cbor - CBOR value to convert
|
|
66
|
+
* @param opts - Formatting options
|
|
67
|
+
* @returns Hex string (possibly annotated)
|
|
68
|
+
*/
|
|
69
|
+
export const hexOpt = (cbor: Cbor, opts: HexFormatOpts = {}): string => {
|
|
70
|
+
if (opts.annotate !== true) {
|
|
71
|
+
return hex(cbor);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const items = dumpItems(cbor, 0, opts);
|
|
75
|
+
const noteColumn = items.reduce((largest, item) => {
|
|
76
|
+
return Math.max(largest, item.formatFirstColumn().length);
|
|
77
|
+
}, 0);
|
|
78
|
+
|
|
79
|
+
// Round up to nearest multiple of 4
|
|
80
|
+
const roundedNoteColumn = ((noteColumn + 4) & ~3) - 1;
|
|
81
|
+
|
|
82
|
+
const lines = items.map((item) => item.format(roundedNoteColumn));
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns the encoded hexadecimal representation of CBOR, with annotations.
|
|
88
|
+
*
|
|
89
|
+
* @param cbor - CBOR value to convert
|
|
90
|
+
* @param tagsStore - Optional tags store for tag name resolution
|
|
91
|
+
* @returns Annotated hex string
|
|
92
|
+
*/
|
|
93
|
+
export const hexAnnotated = (cbor: Cbor, tagsStore?: TagsStore): string => {
|
|
94
|
+
// Use global tags store if not provided
|
|
95
|
+
tagsStore ??= getGlobalTagsStore();
|
|
96
|
+
return hexOpt(cbor, { annotate: true, tagsStore });
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Internal structure for dump items.
|
|
101
|
+
*/
|
|
102
|
+
class DumpItem {
|
|
103
|
+
constructor(
|
|
104
|
+
public level: number,
|
|
105
|
+
public data: Uint8Array[],
|
|
106
|
+
public note?: string,
|
|
107
|
+
) {}
|
|
108
|
+
|
|
109
|
+
format(noteColumn: number): string {
|
|
110
|
+
const column1 = this.formatFirstColumn();
|
|
111
|
+
let column2 = "";
|
|
112
|
+
let padding = "";
|
|
113
|
+
|
|
114
|
+
if (this.note !== undefined) {
|
|
115
|
+
const paddingCount = Math.max(1, Math.min(39, noteColumn) - column1.length + 1);
|
|
116
|
+
padding = " ".repeat(paddingCount);
|
|
117
|
+
column2 = `# ${this.note}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return column1 + padding + column2;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
formatFirstColumn(): string {
|
|
124
|
+
const indent = " ".repeat(this.level * 4);
|
|
125
|
+
const hexParts = this.data.map(bytesToHex).filter((x) => x.length > 0);
|
|
126
|
+
const hexStr = hexParts.join(" ");
|
|
127
|
+
return indent + hexStr;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate dump items for a CBOR value (recursive).
|
|
133
|
+
*/
|
|
134
|
+
function dumpItems(cbor: Cbor, level: number, opts: HexFormatOpts): DumpItem[] {
|
|
135
|
+
const items: DumpItem[] = [];
|
|
136
|
+
|
|
137
|
+
switch (cbor.type) {
|
|
138
|
+
case MajorType.Unsigned: {
|
|
139
|
+
const data = cborData(cbor);
|
|
140
|
+
items.push(new DumpItem(level, [data], `unsigned(${cbor.value})`));
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case MajorType.Negative: {
|
|
145
|
+
const data = cborData(cbor);
|
|
146
|
+
const actualValue = typeof cbor.value === "bigint" ? -1n - cbor.value : -1 - cbor.value;
|
|
147
|
+
items.push(new DumpItem(level, [data], `negative(${actualValue})`));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case MajorType.ByteString: {
|
|
152
|
+
const header = encodeVarInt(cbor.value.length, MajorType.ByteString);
|
|
153
|
+
items.push(new DumpItem(level, [header], `bytes(${cbor.value.length})`));
|
|
154
|
+
|
|
155
|
+
if (cbor.value.length > 0) {
|
|
156
|
+
let note: string | undefined = undefined;
|
|
157
|
+
// Try to decode as UTF-8 string for annotation
|
|
158
|
+
try {
|
|
159
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(cbor.value);
|
|
160
|
+
const sanitizedText = sanitized(text);
|
|
161
|
+
if (sanitizedText !== undefined && sanitizedText !== "") {
|
|
162
|
+
note = flanked(sanitizedText, '"', '"');
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Not valid UTF-8, no annotation
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
items.push(new DumpItem(level + 1, [cbor.value], note));
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case MajorType.Text: {
|
|
174
|
+
const utf8Data = new TextEncoder().encode(cbor.value);
|
|
175
|
+
const header = encodeVarInt(utf8Data.length, MajorType.Text);
|
|
176
|
+
const firstByte = header[0];
|
|
177
|
+
if (firstByte === undefined) {
|
|
178
|
+
throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
|
|
179
|
+
}
|
|
180
|
+
const headerData = [new Uint8Array([firstByte]), header.slice(1)];
|
|
181
|
+
|
|
182
|
+
items.push(new DumpItem(level, headerData, `text(${utf8Data.length})`));
|
|
183
|
+
|
|
184
|
+
items.push(new DumpItem(level + 1, [utf8Data], flanked(cbor.value, '"', '"')));
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case MajorType.Array: {
|
|
189
|
+
const header = encodeVarInt(cbor.value.length, MajorType.Array);
|
|
190
|
+
const firstByte = header[0];
|
|
191
|
+
if (firstByte === undefined) {
|
|
192
|
+
throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
|
|
193
|
+
}
|
|
194
|
+
const headerData = [new Uint8Array([firstByte]), header.slice(1)];
|
|
195
|
+
|
|
196
|
+
items.push(new DumpItem(level, headerData, `array(${cbor.value.length})`));
|
|
197
|
+
|
|
198
|
+
for (const item of cbor.value) {
|
|
199
|
+
items.push(...dumpItems(item, level + 1, opts));
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case MajorType.Map: {
|
|
205
|
+
const header = encodeVarInt(cbor.value.size, MajorType.Map);
|
|
206
|
+
const firstByte = header[0];
|
|
207
|
+
if (firstByte === undefined) {
|
|
208
|
+
throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
|
|
209
|
+
}
|
|
210
|
+
const headerData = [new Uint8Array([firstByte]), header.slice(1)];
|
|
211
|
+
|
|
212
|
+
items.push(new DumpItem(level, headerData, `map(${cbor.value.size})`));
|
|
213
|
+
|
|
214
|
+
for (const entry of cbor.value.entriesArray) {
|
|
215
|
+
items.push(...dumpItems(entry.key, level + 1, opts));
|
|
216
|
+
items.push(...dumpItems(entry.value, level + 1, opts));
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case MajorType.Tagged: {
|
|
222
|
+
const tagValue = cbor.tag;
|
|
223
|
+
if (tagValue === undefined) {
|
|
224
|
+
throw new CborError({ type: "Custom", message: "Tagged CBOR value must have a tag" });
|
|
225
|
+
}
|
|
226
|
+
const header = encodeVarInt(
|
|
227
|
+
typeof tagValue === "bigint" ? Number(tagValue) : tagValue,
|
|
228
|
+
MajorType.Tagged,
|
|
229
|
+
);
|
|
230
|
+
const firstByte = header[0];
|
|
231
|
+
if (firstByte === undefined) {
|
|
232
|
+
throw new CborError({ type: "Custom", message: "Invalid varint encoding" });
|
|
233
|
+
}
|
|
234
|
+
const headerData = [new Uint8Array([firstByte]), header.slice(1)];
|
|
235
|
+
|
|
236
|
+
const noteComponents: string[] = [`tag(${tagValue})`];
|
|
237
|
+
|
|
238
|
+
// Add tag name if tags store is provided
|
|
239
|
+
const numericTagValue = typeof tagValue === "bigint" ? Number(tagValue) : tagValue;
|
|
240
|
+
const tag = createTag(numericTagValue);
|
|
241
|
+
const tagName = opts.tagsStore?.assignedNameForTag(tag);
|
|
242
|
+
if (tagName !== undefined) {
|
|
243
|
+
noteComponents.push(tagName);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const tagNote = noteComponents.join(" ");
|
|
247
|
+
|
|
248
|
+
items.push(new DumpItem(level, headerData, tagNote));
|
|
249
|
+
|
|
250
|
+
items.push(...dumpItems(cbor.value, level + 1, opts));
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case MajorType.Simple: {
|
|
255
|
+
const data = cborData(cbor);
|
|
256
|
+
const simple = cbor.value;
|
|
257
|
+
let note: string;
|
|
258
|
+
|
|
259
|
+
if (simple.type === "True") {
|
|
260
|
+
note = "true";
|
|
261
|
+
} else if (simple.type === "False") {
|
|
262
|
+
note = "false";
|
|
263
|
+
} else if (simple.type === "Null") {
|
|
264
|
+
note = "null";
|
|
265
|
+
} else if (simple.type === "Float") {
|
|
266
|
+
note = `${simple.value}`;
|
|
267
|
+
} else {
|
|
268
|
+
note = "simple";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
items.push(new DumpItem(level, [data], note));
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return items;
|
|
277
|
+
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for CBOR encoding and decoding.
|
|
3
|
+
*
|
|
4
|
+
* @module error
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Tag } from "./tag";
|
|
8
|
+
import { tagToString } from "./tag";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A comprehensive set of errors that can occur during CBOR encoding and
|
|
12
|
+
* decoding operations with special focus on enforcing the deterministic
|
|
13
|
+
* encoding rules specified in the dCBOR specification.
|
|
14
|
+
*
|
|
15
|
+
* The dCBOR implementation validates all encoded CBOR against the
|
|
16
|
+
* deterministic encoding requirements of RFC 8949 §4.2.1, plus additional
|
|
17
|
+
* constraints defined in the dCBOR application profile. These errors represent
|
|
18
|
+
* all the possible validation failures and decoding issues that can arise.
|
|
19
|
+
*/
|
|
20
|
+
export type Error =
|
|
21
|
+
/**
|
|
22
|
+
* The CBOR data ended prematurely during decoding, before a complete CBOR
|
|
23
|
+
* item could be decoded. This typically happens when a CBOR item's
|
|
24
|
+
* structure indicates more data than is actually present.
|
|
25
|
+
*/
|
|
26
|
+
| { readonly type: "Underrun" }
|
|
27
|
+
/**
|
|
28
|
+
* An unsupported or invalid value was encountered in a CBOR header byte.
|
|
29
|
+
* The parameter contains the unsupported header byte value.
|
|
30
|
+
* This can occur when decoding CBOR that uses unsupported features or is
|
|
31
|
+
* malformed.
|
|
32
|
+
*/
|
|
33
|
+
| { readonly type: "UnsupportedHeaderValue"; readonly value: number }
|
|
34
|
+
/**
|
|
35
|
+
* A CBOR numeric value was encoded in a non-canonical form, violating the
|
|
36
|
+
* deterministic encoding requirement of dCBOR (per Section 2.3 of the
|
|
37
|
+
* dCBOR specification).
|
|
38
|
+
*
|
|
39
|
+
* This error is triggered when:
|
|
40
|
+
* - An integer is not encoded in its shortest possible form
|
|
41
|
+
* - A floating point value that could be represented as an integer was not
|
|
42
|
+
* reduced
|
|
43
|
+
* - A NaN value was not encoded in its canonical form (`f97e00`)
|
|
44
|
+
*/
|
|
45
|
+
| { readonly type: "NonCanonicalNumeric" }
|
|
46
|
+
/**
|
|
47
|
+
* An invalid CBOR simple value was encountered during decoding.
|
|
48
|
+
*
|
|
49
|
+
* Per Section 2.4 of the dCBOR specification, only `false`, `true`,
|
|
50
|
+
* `null`, and floating point values are valid simple values in dCBOR.
|
|
51
|
+
* All other major type 7 values are invalid.
|
|
52
|
+
*/
|
|
53
|
+
| { readonly type: "InvalidSimpleValue" }
|
|
54
|
+
/**
|
|
55
|
+
* A CBOR text string was not valid UTF-8. The parameter contains the
|
|
56
|
+
* specific error message.
|
|
57
|
+
*
|
|
58
|
+
* All CBOR text strings (major type 3) must be valid UTF-8 per RFC 8949.
|
|
59
|
+
*/
|
|
60
|
+
| { readonly type: "InvalidString"; readonly message: string }
|
|
61
|
+
/**
|
|
62
|
+
* A CBOR text string was not encoded in Unicode Canonical Normalization
|
|
63
|
+
* Form C (NFC).
|
|
64
|
+
*
|
|
65
|
+
* Per Section 2.5 of the dCBOR specification, all text strings must be in
|
|
66
|
+
* NFC form, and decoders must reject any encoded text strings that are
|
|
67
|
+
* not in NFC.
|
|
68
|
+
*/
|
|
69
|
+
| { readonly type: "NonCanonicalString" }
|
|
70
|
+
/**
|
|
71
|
+
* The decoded CBOR item didn't consume all input data.
|
|
72
|
+
* The parameter contains the number of unused bytes.
|
|
73
|
+
*
|
|
74
|
+
* This error occurs when decoding functions expect exactly one CBOR item
|
|
75
|
+
* but the input contains additional data after a valid CBOR item.
|
|
76
|
+
*/
|
|
77
|
+
| { readonly type: "UnusedData"; readonly count: number }
|
|
78
|
+
/**
|
|
79
|
+
* The keys in a decoded CBOR map were not in the canonical lexicographic order
|
|
80
|
+
* of their encoding.
|
|
81
|
+
*
|
|
82
|
+
* Per the CDE specification and Section 2.1 of dCBOR, map keys must be in
|
|
83
|
+
* ascending lexicographic order of their encoded representation for
|
|
84
|
+
* deterministic encoding.
|
|
85
|
+
*/
|
|
86
|
+
| { readonly type: "MisorderedMapKey" }
|
|
87
|
+
/**
|
|
88
|
+
* A decoded CBOR map contains duplicate keys, which is invalid.
|
|
89
|
+
*
|
|
90
|
+
* Per Section 2.2 of the dCBOR specification, CBOR maps must not contain
|
|
91
|
+
* duplicate keys, and decoders must reject encoded maps with duplicate
|
|
92
|
+
* keys.
|
|
93
|
+
*/
|
|
94
|
+
| { readonly type: "DuplicateMapKey" }
|
|
95
|
+
/**
|
|
96
|
+
* A requested key was not found in a CBOR map during data extraction.
|
|
97
|
+
*/
|
|
98
|
+
| { readonly type: "MissingMapKey" }
|
|
99
|
+
/**
|
|
100
|
+
* A CBOR numeric value could not be represented in the specified target
|
|
101
|
+
* numeric type.
|
|
102
|
+
*
|
|
103
|
+
* This occurs when attempting to convert a CBOR number to a numeric
|
|
104
|
+
* type that is too small to represent the value without loss of
|
|
105
|
+
* precision.
|
|
106
|
+
*/
|
|
107
|
+
| { readonly type: "OutOfRange" }
|
|
108
|
+
/**
|
|
109
|
+
* The CBOR value is not of the expected type for a conversion or
|
|
110
|
+
* operation.
|
|
111
|
+
*
|
|
112
|
+
* This occurs when attempting to convert a CBOR value to a type that
|
|
113
|
+
* doesn't match the actual CBOR item's type (e.g., trying to convert a
|
|
114
|
+
* string to an integer).
|
|
115
|
+
*/
|
|
116
|
+
| { readonly type: "WrongType" }
|
|
117
|
+
/**
|
|
118
|
+
* The CBOR tagged value had a different tag than expected.
|
|
119
|
+
* Contains the expected tag and the actual tag found.
|
|
120
|
+
*/
|
|
121
|
+
| { readonly type: "WrongTag"; readonly expected: Tag; readonly actual: Tag }
|
|
122
|
+
/**
|
|
123
|
+
* Invalid UTF‑8 in a text string.
|
|
124
|
+
*/
|
|
125
|
+
| { readonly type: "InvalidUtf8"; readonly message: string }
|
|
126
|
+
/**
|
|
127
|
+
* Invalid ISO 8601 date format.
|
|
128
|
+
*/
|
|
129
|
+
| { readonly type: "InvalidDate"; readonly message: string }
|
|
130
|
+
/**
|
|
131
|
+
* Custom error message.
|
|
132
|
+
*/
|
|
133
|
+
| { readonly type: "Custom"; readonly message: string };
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a custom error with a message.
|
|
137
|
+
*
|
|
138
|
+
* Matches Rust's `Error::msg()` method.
|
|
139
|
+
*/
|
|
140
|
+
export const errorMsg = (message: string): Error => ({
|
|
141
|
+
type: "Custom",
|
|
142
|
+
message,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Convert an Error to a display string.
|
|
147
|
+
*
|
|
148
|
+
* Matches Rust's `Display` trait / `to_string()` method.
|
|
149
|
+
*/
|
|
150
|
+
export const errorToString = (error: Error): string => {
|
|
151
|
+
switch (error.type) {
|
|
152
|
+
case "Underrun":
|
|
153
|
+
return "early end of CBOR data";
|
|
154
|
+
case "UnsupportedHeaderValue":
|
|
155
|
+
return "unsupported value in CBOR header";
|
|
156
|
+
case "NonCanonicalNumeric":
|
|
157
|
+
return "a CBOR numeric value was encoded in non-canonical form";
|
|
158
|
+
case "InvalidSimpleValue":
|
|
159
|
+
return "an invalid CBOR simple value was encountered";
|
|
160
|
+
case "InvalidString":
|
|
161
|
+
return `an invalidly-encoded UTF-8 string was encountered in the CBOR (${error.message})`;
|
|
162
|
+
case "NonCanonicalString":
|
|
163
|
+
return "a CBOR string was not encoded in Unicode Canonical Normalization Form C";
|
|
164
|
+
case "UnusedData":
|
|
165
|
+
return `the decoded CBOR had ${error.count} extra bytes at the end`;
|
|
166
|
+
case "MisorderedMapKey":
|
|
167
|
+
return "the decoded CBOR map has keys that are not in canonical order";
|
|
168
|
+
case "DuplicateMapKey":
|
|
169
|
+
return "the decoded CBOR map has a duplicate key";
|
|
170
|
+
case "MissingMapKey":
|
|
171
|
+
return "missing CBOR map key";
|
|
172
|
+
case "OutOfRange":
|
|
173
|
+
return "the CBOR numeric value could not be represented in the specified numeric type";
|
|
174
|
+
case "WrongType":
|
|
175
|
+
return "the decoded CBOR value was not the expected type";
|
|
176
|
+
case "WrongTag":
|
|
177
|
+
return `expected CBOR tag ${tagToString(error.expected)}, but got ${tagToString(error.actual)}`;
|
|
178
|
+
case "InvalidUtf8":
|
|
179
|
+
return `invalid UTF‑8 string: ${error.message}`;
|
|
180
|
+
case "InvalidDate":
|
|
181
|
+
return `invalid ISO 8601 date string: ${error.message}`;
|
|
182
|
+
case "Custom":
|
|
183
|
+
return error.message;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Result type matching Rust's `Result<T, Error>`.
|
|
189
|
+
*
|
|
190
|
+
* In TypeScript, we use a discriminated union for Result instead of
|
|
191
|
+
* try/catch for better type safety and Rust compatibility.
|
|
192
|
+
*/
|
|
193
|
+
export type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create a successful Result.
|
|
197
|
+
*/
|
|
198
|
+
export const Ok = <T>(value: T): Result<T> => ({
|
|
199
|
+
ok: true,
|
|
200
|
+
value,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a failed Result.
|
|
205
|
+
*/
|
|
206
|
+
export const Err = <T>(error: Error): Result<T> => ({
|
|
207
|
+
ok: false,
|
|
208
|
+
error,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Typed error class for all CBOR-related errors.
|
|
213
|
+
*
|
|
214
|
+
* Wraps the discriminated union Error type in a JavaScript Error object
|
|
215
|
+
* for proper error handling with stack traces.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* throw new CborError({ type: 'Underrun' });
|
|
220
|
+
* throw new CborError({ type: 'WrongTag', expected: tag1, actual: tag2 });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export class CborError extends Error {
|
|
224
|
+
/**
|
|
225
|
+
* The structured error information.
|
|
226
|
+
*/
|
|
227
|
+
public readonly errorType: Error;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create a new CborError.
|
|
231
|
+
*
|
|
232
|
+
* @param errorType - The discriminated union error type
|
|
233
|
+
* @param message - Optional custom message (defaults to errorToString(errorType))
|
|
234
|
+
*/
|
|
235
|
+
constructor(errorType: Error, message?: string) {
|
|
236
|
+
super(message ?? errorToString(errorType));
|
|
237
|
+
this.name = "CborError";
|
|
238
|
+
this.errorType = errorType;
|
|
239
|
+
|
|
240
|
+
// Maintains proper stack trace for where error was thrown (V8 only)
|
|
241
|
+
if ("captureStackTrace" in Error) {
|
|
242
|
+
(
|
|
243
|
+
Error as {
|
|
244
|
+
captureStackTrace(target: object, constructor: typeof CborError): void;
|
|
245
|
+
}
|
|
246
|
+
).captureStackTrace(this, CborError);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if an error is a CborError.
|
|
252
|
+
*
|
|
253
|
+
* @param error - Error to check
|
|
254
|
+
* @returns True if error is a CborError
|
|
255
|
+
*/
|
|
256
|
+
static isCborError(error: unknown): error is CborError {
|
|
257
|
+
return error instanceof CborError;
|
|
258
|
+
}
|
|
259
|
+
}
|