@kaiko.io/rescript-deser 6.0.0 → 7.0.0-alpha.1

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.
@@ -0,0 +1,189 @@
1
+ open QUnit
2
+ open RescriptCore
3
+
4
+ module Appointment = {
5
+ type t = {
6
+ note: string,
7
+ date: Date.t,
8
+ extra: option<string>,
9
+ }
10
+ module Deserializer = Deser.MakeDeserializer({
11
+ type t = t
12
+ open Deser.Field
13
+
14
+ let fields = Object([("note", String), ("date", Date), ("extra", Optional(String))])
15
+ })
16
+ }
17
+
18
+ module_("Basic deserializer", _ => {
19
+ let valid: array<(_, Appointment.t)> = [
20
+ (
21
+ %raw(`{"note": "Bicentennial doctor's appointment", "date": "2100-01-01"}`),
22
+ {
23
+ note: "Bicentennial doctor's appointment",
24
+ date: Date.fromString("2100-01-01"),
25
+ extra: None,
26
+ },
27
+ ),
28
+ (
29
+ %raw(`{
30
+ "note": "Bicentennial doctor's appointment",
31
+ "date": "2100-01-01",
32
+ "extra": "Don't take your stop-aging pills the night before the appointment",
33
+ }`),
34
+ {
35
+ note: "Bicentennial doctor's appointment",
36
+ date: Date.fromString("2100-01-01"),
37
+ extra: Some("Don't take your stop-aging pills the night before the appointment"),
38
+ },
39
+ ),
40
+ ]
41
+
42
+ test("Correctly deserializes data", qunit => {
43
+ qunit->expect(valid->Array.length)
44
+ valid->Array.forEach(
45
+ ((data, expected)) => {
46
+ Console.log2("Running sample", data)
47
+ switch Appointment.Deserializer.fromJSON(data) {
48
+ | Ok(result) => qunit->deepEqual(result, expected, "result == expected")
49
+ | Error(msg) => Console.error(msg)
50
+ }
51
+ },
52
+ )
53
+ })
54
+
55
+ let invalid = [
56
+ (
57
+ "Missing non-optional field",
58
+ %raw(`{"extra": "Bicentennial doctor's appointment", "date": "2100-01-01"}`),
59
+ ),
60
+ ]
61
+
62
+ test("Correctly catches invalid data", qunit => {
63
+ qunit->expect(invalid->Array.length)
64
+ invalid->Array.forEach(
65
+ ((message, data)) => {
66
+ Console.log3("Running sample", message, data)
67
+ switch Appointment.Deserializer.fromJSON(data) {
68
+ | Ok(result) => Console.error2("Invalid being accepted: ", result)
69
+ | Error(msg) => {
70
+ Console.log2("Correctly detected:", msg)
71
+ qunit->ok(true, true)
72
+ }
73
+ }
74
+ },
75
+ )
76
+ })
77
+ })
78
+
79
+ module_("Recursive deserializer", _ => {
80
+ module TrivialTree = {
81
+ type rec t<'a> = {
82
+ data: 'a,
83
+ children: array<t<'a>>,
84
+ }
85
+
86
+ module DeserializerImpl = Deser.MakeDeserializer({
87
+ type payload
88
+ type rec t = {
89
+ data: payload,
90
+ children: array<t>,
91
+ }
92
+ open Deser.Field
93
+
94
+ let fields = Object([("data", Any), ("children", Array(Self))])
95
+ })
96
+
97
+ module Deserializer = {
98
+ include DeserializerImpl
99
+
100
+ let fromJSON = data => data->DeserializerImpl.fromJSON->Result.map(x => x->Obj.magic)
101
+ }
102
+ }
103
+
104
+ let valid: array<(_, TrivialTree.t<string>)> = [
105
+ (
106
+ %raw(`{"data": "A", "children": [{"data": "A1", "children": []}, {"data": "B", "children": [{"data": "C", "children": []}, {"data": "D", "children": []}]}]}`),
107
+ {
108
+ data: "A",
109
+ children: [
110
+ {data: "A1", children: []},
111
+ {data: "B", children: [{data: "C", children: []}, {data: "D", children: []}]},
112
+ ],
113
+ },
114
+ ),
115
+ ]
116
+
117
+ test("Trivial recursion detection: Ok", qunit => {
118
+ qunit->expect(1)
119
+ qunit->deepEqual(TrivialTree.Deserializer.checkFieldsSanity(), Ok(), "Ok")
120
+ })
121
+
122
+ test("Infinite list", qunit => {
123
+ module InfiniteList = Deser.MakeDeserializer({
124
+ open Deser.Field
125
+
126
+ type t
127
+ let fields = Object([("head", String), ("tail", Self)])
128
+ })
129
+
130
+ qunit->expect(1)
131
+ qunit->deepEqual(InfiniteList.checkFieldsSanity()->Result.isError, true, "Ok")
132
+ })
133
+
134
+ test("Finite list", qunit => {
135
+ module List = Deser.MakeDeserializer({
136
+ open Deser.Field
137
+
138
+ type t
139
+ let fields = Object([("head", String), ("tail", Optional(Self))])
140
+ })
141
+
142
+ qunit->expect(1)
143
+ qunit->deepEqual(List.checkFieldsSanity(), Ok(), "Ok")
144
+ })
145
+
146
+ test("Correctly deserializes recursive data", qunit => {
147
+ qunit->expect(valid->Array.length)
148
+ valid->Array.forEach(
149
+ ((data, expected)) => {
150
+ Console.log2("Running sample", data)
151
+ switch TrivialTree.Deserializer.fromJSON(data) {
152
+ | Ok(result) => qunit->deepEqual(result, expected, "result == expected")
153
+ | Error(msg) => Console.error(msg)
154
+ }
155
+ },
156
+ )
157
+ })
158
+
159
+ test("Recursion in sub-deserializer", qunit => {
160
+ module List = Deser.MakeDeserializer({
161
+ open Deser.Field
162
+
163
+ type t
164
+ let fields = Object([("head", String), ("tail", Optional(Self))])
165
+ })
166
+
167
+ module Ledger = Deser.MakeDeserializer({
168
+ open Deser.Field
169
+
170
+ type t
171
+ let fields = Object([("records", Deserializer(module(List))), ("next", Optional(Self))])
172
+ })
173
+
174
+ let data = %raw(`
175
+ {"records": {"head": "A", "tail": {"head": "B"}},
176
+ "next": {"records": {"head": "A", "tail": {"head": "B"}}}}
177
+ `)
178
+ let expected = {
179
+ "records": {"head": "A", "tail": {"head": "B", "tail": None}},
180
+ "next": {
181
+ "records": {"head": "A", "tail": {"head": "B", "tail": None}},
182
+ "next": None,
183
+ },
184
+ }
185
+
186
+ qunit->expect(1)
187
+ qunit->deepEqual(data->Ledger.fromJSON->Obj.magic, Ok(expected), "nice ledger")
188
+ })
189
+ })
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,6 +1,5 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Js_json from "rescript/lib/es6/js_json.js";
4
3
  import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
5
4
  import * as Core__Dict from "@rescript/core/lib/es6/src/Core__Dict.js";
6
5
  import * as Caml_option from "rescript/lib/es6/caml_option.js";
@@ -138,28 +137,26 @@ function toString(type_) {
138
137
  }
139
138
 
140
139
  function _taggedToString(tagged) {
141
- if (typeof tagged !== "object") {
142
- switch (tagged) {
143
- case "JSONFalse" :
144
- return "Boolean(false)";
145
- case "JSONTrue" :
140
+ if (!Array.isArray(tagged) && (tagged === null || typeof tagged !== "object") && typeof tagged !== "number" && typeof tagged !== "string" && typeof tagged !== "boolean") {
141
+ return "Null";
142
+ }
143
+ if (Array.isArray(tagged)) {
144
+ return "Array(" + Core__Option.getOr(JSON.stringify(tagged), "...") + ")";
145
+ }
146
+ switch (typeof tagged) {
147
+ case "boolean" :
148
+ if (tagged) {
146
149
  return "Boolean(true)";
147
- case "JSONNull" :
148
- return "Null";
149
-
150
- }
151
- } else {
152
- switch (tagged.TAG) {
153
- case "JSONString" :
154
- return "String(\"" + tagged._0 + "\")";
155
- case "JSONNumber" :
156
- return "Number(" + tagged._0.toString() + ")";
157
- case "JSONObject" :
158
- return "Object(" + Core__Option.getOr(JSON.stringify(tagged._0), "...") + ")";
159
- case "JSONArray" :
160
- return "Array(" + Core__Option.getOr(JSON.stringify(tagged._0), "...") + ")";
161
-
162
- }
150
+ } else {
151
+ return "Boolean(false)";
152
+ }
153
+ case "string" :
154
+ return "String(\"" + tagged + "\")";
155
+ case "number" :
156
+ return "Number(" + tagged.toString() + ")";
157
+ case "object" :
158
+ return "Object(" + Core__Option.getOr(JSON.stringify(tagged), "...") + ")";
159
+
163
160
  }
164
161
  }
165
162
 
@@ -189,93 +186,34 @@ function extractValue(values, field, shape, self) {
189
186
  function fromUntagged(untagged, _shape, self) {
190
187
  while(true) {
191
188
  var shape = _shape;
192
- var match = Js_json.classify(untagged);
193
189
  var exit = 0;
194
- var s;
195
- var f;
196
190
  if (typeof shape !== "object") {
197
191
  switch (shape) {
198
192
  case "Any" :
199
193
  return untagged;
200
194
  case "String" :
201
- if (typeof match !== "object") {
202
- exit = 1;
203
- } else {
204
- if (match.TAG === "JSONString") {
205
- return match._0;
206
- }
207
- exit = 1;
195
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "string") {
196
+ return untagged;
208
197
  }
209
198
  break;
210
199
  case "Int" :
211
- if (typeof match !== "object") {
212
- exit = 1;
213
- } else {
214
- if (match.TAG === "JSONNumber") {
215
- return match._0 | 0;
216
- }
217
- exit = 1;
200
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "number") {
201
+ return untagged | 0;
218
202
  }
219
203
  break;
220
204
  case "Float" :
221
- if (typeof match !== "object") {
222
- exit = 1;
223
- } else {
224
- if (match.TAG === "JSONNumber") {
225
- return match._0;
226
- }
227
- exit = 1;
205
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "number") {
206
+ return untagged;
228
207
  }
229
208
  break;
230
209
  case "Boolean" :
231
- if (typeof match !== "object") {
232
- switch (match) {
233
- case "JSONFalse" :
234
- return false;
235
- case "JSONTrue" :
236
- return true;
237
- default:
238
- exit = 1;
239
- }
240
- } else {
241
- exit = 1;
210
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "boolean") {
211
+ return untagged;
242
212
  }
243
213
  break;
244
214
  case "Date" :
245
- if (typeof match !== "object") {
246
- exit = 1;
247
- } else {
248
- switch (match.TAG) {
249
- case "JSONString" :
250
- s = match._0;
251
- exit = 2;
252
- break;
253
- case "JSONNumber" :
254
- f = match._0;
255
- exit = 3;
256
- break;
257
- default:
258
- exit = 1;
259
- }
260
- }
261
- break;
262
215
  case "Datetime" :
263
- if (typeof match !== "object") {
264
- exit = 1;
265
- } else {
266
- switch (match.TAG) {
267
- case "JSONString" :
268
- s = match._0;
269
- exit = 2;
270
- break;
271
- case "JSONNumber" :
272
- f = match._0;
273
- exit = 3;
274
- break;
275
- default:
276
- exit = 1;
277
- }
278
- }
216
+ exit = 2;
279
217
  break;
280
218
  case "Self" :
281
219
  _shape = self;
@@ -285,133 +223,91 @@ function fromUntagged(untagged, _shape, self) {
285
223
  } else {
286
224
  switch (shape.TAG) {
287
225
  case "Literal" :
288
- if (typeof match !== "object") {
289
- exit = 1;
290
- } else {
291
- if (match.TAG === "JSONString") {
292
- var text = match._0;
293
- var expected = shape._0;
294
- if (text === expected) {
295
- return text;
296
- }
297
- throw {
298
- RE_EXN_ID: $$TypeError,
299
- _1: "Expecting literal " + expected + ", got " + text,
300
- Error: new Error()
301
- };
226
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "string") {
227
+ var expected = shape._0;
228
+ if (untagged === expected) {
229
+ return untagged;
302
230
  }
303
- exit = 1;
231
+ throw {
232
+ RE_EXN_ID: $$TypeError,
233
+ _1: "Expecting literal " + expected + ", got " + untagged,
234
+ Error: new Error()
235
+ };
304
236
  }
305
237
  break;
306
238
  case "Array" :
307
- if (typeof match !== "object") {
308
- exit = 1;
309
- } else {
310
- if (match.TAG === "JSONArray") {
311
- var shape$1 = shape._0;
312
- return match._0.map((function(shape$1){
313
- return function (item) {
314
- return fromUntagged(item, shape$1, self);
315
- }
316
- }(shape$1)));
317
- }
318
- exit = 1;
239
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && Array.isArray(untagged)) {
240
+ var shape$1 = shape._0;
241
+ return untagged.map((function(shape$1){
242
+ return function (item) {
243
+ return fromUntagged(item, shape$1, self);
244
+ }
245
+ }(shape$1)));
319
246
  }
320
247
  break;
321
248
  case "Tuple" :
322
- if (typeof match !== "object") {
323
- exit = 1;
324
- } else {
325
- if (match.TAG === "JSONArray") {
326
- var items = match._0;
327
- var bases = shape._0;
328
- var lenbases = bases.length;
329
- var lenitems = items.length;
330
- if (lenbases === lenitems) {
331
- return Belt_Array.zipBy(items, bases, (function (i, b) {
332
- return fromUntagged(i, b, self);
333
- }));
334
- }
335
- throw {
336
- RE_EXN_ID: $$TypeError,
337
- _1: "Expecting " + lenbases.toString() + " items, got " + lenitems.toString(),
338
- Error: new Error()
339
- };
249
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && Array.isArray(untagged)) {
250
+ var bases = shape._0;
251
+ var lenbases = bases.length;
252
+ var lenitems = untagged.length;
253
+ if (lenbases === lenitems) {
254
+ return Belt_Array.zipBy(untagged, bases, (function (i, b) {
255
+ return fromUntagged(i, b, self);
256
+ }));
340
257
  }
341
- exit = 1;
258
+ throw {
259
+ RE_EXN_ID: $$TypeError,
260
+ _1: "Expecting " + lenbases.toString() + " items, got " + lenitems.toString(),
261
+ Error: new Error()
262
+ };
342
263
  }
343
264
  break;
344
265
  case "Object" :
345
- if (typeof match !== "object") {
346
- exit = 1;
347
- } else {
348
- if (match.TAG === "JSONObject") {
349
- var values = match._0;
350
- return Object.fromEntries(shape._0.map((function(values){
351
- return function (param) {
352
- var field = param[0];
353
- var value;
354
- try {
355
- value = extractValue(values, field, param[1], self);
356
- }
357
- catch (raw_msg){
358
- var msg = Caml_js_exceptions.internalToOCamlException(raw_msg);
359
- if (msg.RE_EXN_ID === $$TypeError) {
360
- throw {
361
- RE_EXN_ID: $$TypeError,
362
- _1: "Field \"" + field + "\": " + msg._1,
363
- Error: new Error()
364
- };
365
- }
366
- throw msg;
266
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "object" && !Array.isArray(untagged)) {
267
+ return Object.fromEntries(shape._0.map(function (param) {
268
+ var field = param[0];
269
+ var value;
270
+ try {
271
+ value = extractValue(untagged, field, param[1], self);
272
+ }
273
+ catch (raw_msg){
274
+ var msg = Caml_js_exceptions.internalToOCamlException(raw_msg);
275
+ if (msg.RE_EXN_ID === $$TypeError) {
276
+ throw {
277
+ RE_EXN_ID: $$TypeError,
278
+ _1: "Field \"" + field + "\": " + msg._1,
279
+ Error: new Error()
280
+ };
367
281
  }
368
- return [
369
- field,
370
- value
371
- ];
282
+ throw msg;
372
283
  }
373
- }(values))));
374
- }
375
- exit = 1;
284
+ return [
285
+ field,
286
+ value
287
+ ];
288
+ }));
376
289
  }
377
290
  break;
378
291
  case "Optional" :
379
- var shape$2 = shape._0;
380
- if (typeof match !== "object") {
381
- if (match === "JSONNull") {
382
- return undefined;
383
- }
384
- _shape = shape$2;
385
- continue ;
386
- } else {
387
- _shape = shape$2;
388
- continue ;
292
+ if (!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") {
293
+ return undefined;
389
294
  }
295
+ _shape = shape._0;
296
+ continue ;
390
297
  case "OptionalWithDefault" :
391
- var shape$3 = shape._0;
392
- if (typeof match !== "object") {
393
- if (match === "JSONNull") {
394
- return shape._1;
395
- }
396
- _shape = shape$3;
397
- continue ;
398
- } else {
399
- _shape = shape$3;
400
- continue ;
298
+ if (!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") {
299
+ return shape._1;
401
300
  }
301
+ _shape = shape._0;
302
+ continue ;
402
303
  case "Mapping" :
403
- if (typeof match !== "object") {
404
- exit = 1;
405
- } else {
406
- if (match.TAG === "JSONObject") {
407
- var f$1 = shape._0;
408
- return Core__Dict.mapValues(match._0, (function(f$1){
409
- return function (v) {
410
- return fromUntagged(v, f$1, self);
411
- }
412
- }(f$1)));
413
- }
414
- exit = 1;
304
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && typeof untagged === "object" && !Array.isArray(untagged)) {
305
+ var f = shape._0;
306
+ return Core__Dict.mapValues(untagged, (function(f){
307
+ return function (v) {
308
+ return fromUntagged(v, f, self);
309
+ }
310
+ }(f)));
415
311
  }
416
312
  break;
417
313
  case "Deserializer" :
@@ -425,17 +321,15 @@ function fromUntagged(untagged, _shape, self) {
425
321
  Error: new Error()
426
322
  };
427
323
  case "Collection" :
428
- if (typeof match !== "object") {
429
- exit = 1;
430
- } else {
431
- if (match.TAG === "JSONArray") {
432
- return match._0.map(shape._0.fromJSON).filter(function (x) {
433
- return Core__Result.isOk(x);
434
- }).map(function (prim) {
435
- return prim;
436
- });
437
- }
438
- exit = 1;
324
+ if (!(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean") && Array.isArray(untagged)) {
325
+ return Core__Array.filterMap(untagged.map(shape._0.fromJSON), (function (x) {
326
+ if (x.TAG === "Ok") {
327
+ return Caml_option.some(x._0);
328
+ }
329
+ console.warn("Deser", "Could not deserialize value in the collection", x._0);
330
+ })).map(function (prim) {
331
+ return prim;
332
+ });
439
333
  }
440
334
  break;
441
335
  case "DefaultWhenInvalid" :
@@ -455,35 +349,37 @@ function fromUntagged(untagged, _shape, self) {
455
349
 
456
350
  }
457
351
  }
458
- switch (exit) {
459
- case 1 :
460
- throw {
461
- RE_EXN_ID: $$TypeError,
462
- _1: "Expected " + toString(shape) + ", but got " + _taggedToString(match) + " instead",
463
- Error: new Error()
464
- };
465
- case 2 :
466
- var r = new Date(s);
467
- if (Number.isNaN(r.getDate())) {
468
- throw {
469
- RE_EXN_ID: $$TypeError,
470
- _1: "Invalid date " + s,
471
- Error: new Error()
472
- };
473
- }
474
- return r;
475
- case 3 :
476
- var r$1 = new Date(f);
477
- if (Number.isNaN(r$1.getDate())) {
478
- throw {
479
- RE_EXN_ID: $$TypeError,
480
- _1: "Invalid date " + f.toString(),
481
- Error: new Error()
482
- };
483
- }
484
- return r$1;
485
-
352
+ if (exit === 2 && !(!Array.isArray(untagged) && (untagged === null || typeof untagged !== "object") && typeof untagged !== "number" && typeof untagged !== "string" && typeof untagged !== "boolean")) {
353
+ switch (typeof untagged) {
354
+ case "string" :
355
+ var r = new Date(untagged);
356
+ if (isNaN(r.getDate())) {
357
+ throw {
358
+ RE_EXN_ID: $$TypeError,
359
+ _1: "Invalid date " + untagged,
360
+ Error: new Error()
361
+ };
362
+ }
363
+ return r;
364
+ case "number" :
365
+ var r$1 = new Date(untagged);
366
+ if (isNaN(r$1.getDate())) {
367
+ throw {
368
+ RE_EXN_ID: $$TypeError,
369
+ _1: "Invalid date " + untagged.toString(),
370
+ Error: new Error()
371
+ };
372
+ }
373
+ return r$1;
374
+ default:
375
+
376
+ }
486
377
  }
378
+ throw {
379
+ RE_EXN_ID: $$TypeError,
380
+ _1: "Expected " + toString(shape) + ", but got " + _taggedToString(untagged) + " instead",
381
+ Error: new Error()
382
+ };
487
383
  };
488
384
  }
489
385
 
@@ -616,7 +512,7 @@ var Field = {
616
512
 
617
513
  function MakeDeserializer(S) {
618
514
  var fields = S.fields;
619
- var name = "Deserializer " + "Deser" + ", " + "File \"Deser.res\", line 322, characters 39-47";
515
+ var name = "Deserializer " + "Deser" + ", " + "File \"Deser.res\", line 328, characters 39-47";
620
516
  var checkFieldsSanity$1 = function () {
621
517
  return checkFieldsSanity(name, fields, false);
622
518
  };