@microsoft/ccf-app 5.0.0-dev13 → 5.0.0-dev14

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/converters.d.ts CHANGED
@@ -157,6 +157,32 @@ export declare const string: DataConverter<string>;
157
157
  * ```
158
158
  */
159
159
  export declare const json: <T extends JsonCompatible<T>>() => DataConverter<T>;
160
+ /**
161
+ * Returns a converter for JSON-compatible objects or values, with errors for
162
+ * known-incompatible types.
163
+ *
164
+ * Based on {@linkcode json}, but additionally runs a check during every encode
165
+ * call, throwing an error if the object contains fields which cannot be round-tripped
166
+ * to JSON (Date, Map). This incurs some cost in checking each instance, but gives
167
+ * clear errors rather than late serdes mismatches.
168
+ *
169
+ * Example:
170
+ * ```
171
+ * interface Data {
172
+ * m: Map<string, string>
173
+ * }
174
+ * const d: Data = { m: new Map<string, string>() };
175
+ * d.m.set("hello", "John");
176
+ *
177
+ * const conv = ccfapp.json<Data>();
178
+ * const buffer = conv.encode(d); // ArrayBuffer, but contents of map silently lost!
179
+ * const d2 = conv.decode(buffer); // Data, but doesn't match d!
180
+ *
181
+ * const convChecked = ccfapp.checkedJson<Data>();
182
+ * const buffer2 = convChecked.encode(d); // Throws TypeError
183
+ * ```
184
+ */
185
+ export declare const checkedJson: <T extends JsonCompatible<T>>() => DataConverter<T>;
160
186
  /**
161
187
  * Returns a converter for [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) objects.
162
188
  *
package/converters.js CHANGED
@@ -29,6 +29,12 @@ function checkNumber(val) {
29
29
  throw new TypeError(`Value ${val} is not a number`);
30
30
  }
31
31
  }
32
+ function checkInt(val) {
33
+ checkNumber(val);
34
+ if (!Number.isInteger(val)) {
35
+ throw new TypeError(`Value ${val} is not an integer`);
36
+ }
37
+ }
32
38
  function checkBigInt(val) {
33
39
  if (typeof val !== "bigint") {
34
40
  throw new TypeError(`Value ${val} is not a bigint`);
@@ -39,6 +45,23 @@ function checkString(val) {
39
45
  throw new TypeError(`Value ${val} is not a string`);
40
46
  }
41
47
  }
48
+ function checkJsonSafe(val) {
49
+ // Hard to be exhaustive, but throw errors for any Map or Date elements found
50
+ if (val instanceof Map) {
51
+ throw TypeError(`Value contains a Map, which cannot be converted to JSON`);
52
+ }
53
+ if (val instanceof Date) {
54
+ throw TypeError(`Value contains a Date, which cannot be converted back from JSON`);
55
+ }
56
+ if (typeof val === "object") {
57
+ if (Array.isArray(val)) {
58
+ val.every((e) => checkJsonSafe(e));
59
+ }
60
+ else if (val !== null) {
61
+ Object.entries(val).every(([k, v]) => checkJsonSafe(v));
62
+ }
63
+ }
64
+ }
42
65
  class BoolConverter {
43
66
  encode(val) {
44
67
  checkBoolean(val);
@@ -52,7 +75,7 @@ class BoolConverter {
52
75
  }
53
76
  class Int8Converter {
54
77
  encode(val) {
55
- checkNumber(val);
78
+ checkInt(val);
56
79
  if (val < -128 || val > 127) {
57
80
  throw new RangeError("value is not within int8 range");
58
81
  }
@@ -66,7 +89,7 @@ class Int8Converter {
66
89
  }
67
90
  class Uint8Converter {
68
91
  encode(val) {
69
- checkNumber(val);
92
+ checkInt(val);
70
93
  if (val < 0 || val > 255) {
71
94
  throw new RangeError("value is not within uint8 range");
72
95
  }
@@ -80,7 +103,7 @@ class Uint8Converter {
80
103
  }
81
104
  class Int16Converter {
82
105
  encode(val) {
83
- checkNumber(val);
106
+ checkInt(val);
84
107
  if (val < -32768 || val > 32767) {
85
108
  throw new RangeError("value is not within int16 range");
86
109
  }
@@ -94,7 +117,7 @@ class Int16Converter {
94
117
  }
95
118
  class Uint16Converter {
96
119
  encode(val) {
97
- checkNumber(val);
120
+ checkInt(val);
98
121
  if (val < 0 || val > 65535) {
99
122
  throw new RangeError("value is not within uint16 range");
100
123
  }
@@ -108,7 +131,7 @@ class Uint16Converter {
108
131
  }
109
132
  class Int32Converter {
110
133
  encode(val) {
111
- checkNumber(val);
134
+ checkInt(val);
112
135
  if (val < -2147483648 || val > 2147483647) {
113
136
  throw new RangeError("value is not within int32 range");
114
137
  }
@@ -122,7 +145,7 @@ class Int32Converter {
122
145
  }
123
146
  class Uint32Converter {
124
147
  encode(val) {
125
- checkNumber(val);
148
+ checkInt(val);
126
149
  if (val < 0 || val > 4294967295) {
127
150
  throw new RangeError("value is not within uint32 range");
128
151
  }
@@ -195,6 +218,12 @@ class JSONConverter {
195
218
  return ccf.bufToJsonCompatible(buf);
196
219
  }
197
220
  }
221
+ class CheckedJSONConverter extends JSONConverter {
222
+ encode(val) {
223
+ checkJsonSafe(val);
224
+ return super.encode(val);
225
+ }
226
+ }
198
227
  class TypedArrayConverter {
199
228
  constructor(clazz) {
200
229
  this.clazz = clazz;
@@ -364,6 +393,32 @@ export const string = new StringConverter();
364
393
  * ```
365
394
  */
366
395
  export const json = () => new JSONConverter();
396
+ /**
397
+ * Returns a converter for JSON-compatible objects or values, with errors for
398
+ * known-incompatible types.
399
+ *
400
+ * Based on {@linkcode json}, but additionally runs a check during every encode
401
+ * call, throwing an error if the object contains fields which cannot be round-tripped
402
+ * to JSON (Date, Map). This incurs some cost in checking each instance, but gives
403
+ * clear errors rather than late serdes mismatches.
404
+ *
405
+ * Example:
406
+ * ```
407
+ * interface Data {
408
+ * m: Map<string, string>
409
+ * }
410
+ * const d: Data = { m: new Map<string, string>() };
411
+ * d.m.set("hello", "John");
412
+ *
413
+ * const conv = ccfapp.json<Data>();
414
+ * const buffer = conv.encode(d); // ArrayBuffer, but contents of map silently lost!
415
+ * const d2 = conv.decode(buffer); // Data, but doesn't match d!
416
+ *
417
+ * const convChecked = ccfapp.checkedJson<Data>();
418
+ * const buffer2 = convChecked.encode(d); // Throws TypeError
419
+ * ```
420
+ */
421
+ export const checkedJson = () => new CheckedJSONConverter();
367
422
  /**
368
423
  * Returns a converter for [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) objects.
369
424
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/ccf-app",
3
- "version": "5.0.0-dev13",
3
+ "version": "5.0.0-dev14",
4
4
  "description": "CCF app support package",
5
5
  "main": "index.js",
6
6
  "files": [