@kaiko.io/rescript-deser 4.0.0-rc.2 → 4.0.0-rc.4

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/lib/bs/.bsdeps CHANGED
@@ -3,6 +3,6 @@
3
3
  0
4
4
  rescript.json 0x1.94b3a6a1d2ba4p+30
5
5
  tests 0x1.93fb8b23e3f01p+30
6
- src/ 0x1.94b3a6a1d2ba4p+30
6
+ src/ 0x1.957e56214e505p+30
7
7
  ===
8
8
  /home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/rescript.exe 0x1.9578a22fcfdf4p+30
@@ -1,2 +1,2 @@
1
- #Start(1700670041426)
2
- #Done(1700670041426)
1
+ #Start(1700765549343)
2
+ #Done(1700765549343)
package/lib/bs/.ninja_log CHANGED
@@ -1,19 +1,27 @@
1
1
  # ninja log v6
2
- 0 6 1700670039254870652 tests/QUnit.ast a6a94a20e20f8557
3
- 1 7 1700670039254870652 tests/index.ast 3c91d8240b8bb4b8
4
- 1 8 1700670039254870652 src/JSON.ast 54dacc9e71f78ec4
5
- 6 9 1700670039254870652 tests/QUnit.d 4a2631c992d93ce7
6
- 7 9 1700670039254870652 tests/index.d 5a4db9cdecd186b3
7
- 8 10 1700670039258204077 src/JSON.d cd2d6773c781340f
8
- 9 15 1700670039261537502 tests/QUnit.cmj 1baf089a71e93e14
9
- 9 15 1700670039261537502 tests/QUnit.cmi 1baf089a71e93e14
10
- 9 15 1700670039261537502 ../es6/tests/QUnit.js 1baf089a71e93e14
11
- 9 15 1700670039261537502 ../js/tests/QUnit.js 1baf089a71e93e14
12
- 10 28 1700670039274871200 src/JSON.cmj 5c6bd34c8b98f8b9
13
- 10 28 1700670039274871200 src/JSON.cmi 5c6bd34c8b98f8b9
14
- 10 28 1700670039274871200 ../es6/src/JSON.js 5c6bd34c8b98f8b9
15
- 10 28 1700670039274871200 ../js/src/JSON.js 5c6bd34c8b98f8b9
16
- 28 38 1700670039284871474 tests/index.cmj 8a0bf0b0432467b0
17
- 28 38 1700670039284871474 tests/index.cmi 8a0bf0b0432467b0
18
- 28 38 1700670039284871474 ../es6/tests/index.js 8a0bf0b0432467b0
19
- 28 38 1700670039284871474 ../js/tests/index.js 8a0bf0b0432467b0
2
+ 0 7 1700763106509919117 src/JSON.ast 3df97f11bfe4e9b4
3
+ 7 9 1700763106513252576 src/JSON.d 54f2190af6eab30f
4
+ 9 26 1700763106529919867 src/JSON.cmj f5326fd4af8f39e2
5
+ 9 26 1700763106529919867 src/JSON.cmi f5326fd4af8f39e2
6
+ 9 26 1700763106529919867 ../es6/src/JSON.js f5326fd4af8f39e2
7
+ 0 16 1700763106513252576 src/JSON.cmj 9477bd76994296b7
8
+ 0 16 1700763106513252576 src/JSON.cmi 9477bd76994296b7
9
+ 0 16 1700763106513252576 ../es6/src/JSON.js 9477bd76994296b7
10
+ 0 6 1700765531805623915 tests/QUnit.ast a6a94a20e20f8557
11
+ 1 6 1700765531805623915 tests/index.ast 3c91d8240b8bb4b8
12
+ 0 8 1700765531808957333 src/JSON.ast 54dacc9e71f78ec4
13
+ 6 8 1700765531808957333 tests/QUnit.d 4a2631c992d93ce7
14
+ 6 8 1700765531808957333 tests/index.d 5a4db9cdecd186b3
15
+ 8 10 1700765531808957333 src/JSON.d cd2d6773c781340f
16
+ 8 15 1700765531815624169 tests/QUnit.cmj e2ba8efce8af03b9
17
+ 8 15 1700765531815624169 tests/QUnit.cmi e2ba8efce8af03b9
18
+ 8 15 1700765531815624169 ../es6/tests/QUnit.js e2ba8efce8af03b9
19
+ 8 15 1700765531815624169 ../js/tests/QUnit.js e2ba8efce8af03b9
20
+ 10 28 1700765531828957841 src/JSON.cmj 3da9e5f359841f1a
21
+ 10 28 1700765531828957841 src/JSON.cmi 3da9e5f359841f1a
22
+ 10 28 1700765531828957841 ../es6/src/JSON.js 3da9e5f359841f1a
23
+ 10 28 1700765531828957841 ../js/src/JSON.js 3da9e5f359841f1a
24
+ 28 38 1700765531838958094 tests/index.cmj 630554c615a94d8d
25
+ 28 38 1700765531838958094 tests/index.cmi 630554c615a94d8d
26
+ 28 38 1700765531838958094 ../es6/tests/index.js 630554c615a94d8d
27
+ 28 38 1700765531838958094 ../js/tests/index.js 630554c615a94d8d
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -110,11 +110,11 @@ function toString(type_) {
110
110
  case "Array" :
111
111
  return "Array of " + toString(type_._0);
112
112
  case "Tuple" :
113
- return "Tuple of (" + Prelude.$$Array.map(type_._0, toString).join(", ") + ")";
113
+ return "Tuple of (" + type_._0.map(toString).join(", ") + ")";
114
114
  case "Object" :
115
- var desc = Prelude.$$Array.map(type_._0, (function (param) {
116
- return param[0] + ": " + toString(param[1]);
117
- })).join(", ");
115
+ var desc = type_._0.map(function (param) {
116
+ return param[0] + ": " + toString(param[1]);
117
+ }).join(", ");
118
118
  return "Object of {" + desc + "}";
119
119
  case "Optional" :
120
120
  case "OptionalWithDefault" :
@@ -306,7 +306,7 @@ function fromUntagged(untagged, _shape, self) {
306
306
  } else {
307
307
  if (match.TAG === "JSONArray") {
308
308
  var shape$1 = shape._0;
309
- return Prelude.$$Array.map(match._0, (function(shape$1){
309
+ return match._0.map((function(shape$1){
310
310
  return function (item) {
311
311
  return fromUntagged(item, shape$1, self);
312
312
  }
@@ -344,7 +344,7 @@ function fromUntagged(untagged, _shape, self) {
344
344
  } else {
345
345
  if (match.TAG === "JSONObject") {
346
346
  var values = match._0;
347
- return Curry._1(Prelude.Dict.fromArray, Prelude.$$Array.map(shape._0, (function(values){
347
+ return Curry._1(Prelude.Dict.fromArray, shape._0.map((function(values){
348
348
  return function (param) {
349
349
  var field = param[0];
350
350
  var value;
@@ -426,9 +426,13 @@ function fromUntagged(untagged, _shape, self) {
426
426
  exit = 1;
427
427
  } else {
428
428
  if (match.TAG === "JSONArray") {
429
- return Prelude.$$Array.map(Prelude.$$Array.keepSome(Prelude.$$Array.map(Prelude.$$Array.map(match._0, shape._0.fromJSON), Prelude.Result.warn)), (function (prim) {
430
- return prim;
431
- }));
429
+ return Prelude.$$Array.keepSome(match._0.map((function(shape){
430
+ return function (param) {
431
+ return Curry._1(shape._0.fromJSON, param);
432
+ }
433
+ }(shape))).map(Prelude.Result.warn)).map(function (prim) {
434
+ return prim;
435
+ });
432
436
  }
433
437
  exit = 1;
434
438
  }
@@ -518,7 +522,7 @@ function checkFieldsSanity(name, _fields, _optional) {
518
522
  } else {
519
523
  switch (fields.TAG) {
520
524
  case "Tuple" :
521
- return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(Prelude.$$Array.mapWithIndex(fields._0, (function(optional){
525
+ return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
522
526
  return function (field, index) {
523
527
  return function () {
524
528
  return checkFieldsSanity(name + "[" + String(index) + "]", field, optional);
@@ -528,7 +532,7 @@ function checkFieldsSanity(name, _fields, _optional) {
528
532
 
529
533
  }));
530
534
  case "Object" :
531
- return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(Prelude.$$Array.map(fields._0, (function(optional){
535
+ return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
532
536
  return function (param) {
533
537
  var field = param[1];
534
538
  var fieldName = param[0];
@@ -606,7 +610,7 @@ var Field = {
606
610
 
607
611
  function MakeDeserializer(S) {
608
612
  var fields = S.fields;
609
- var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line 329, characters 39-47";
613
+ var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line 320, characters 39-47";
610
614
  var checkFieldsSanity$1 = function () {
611
615
  return checkFieldsSanity(name, fields, false);
612
616
  };
@@ -630,35 +634,9 @@ function MakeDeserializer(S) {
630
634
  _0: res
631
635
  };
632
636
  };
633
- var fromAny = function (any) {
634
- var match = typeof any;
635
- if (match === "string") {
636
- var parsed;
637
- try {
638
- parsed = JSON.parse(any);
639
- }
640
- catch (exn){
641
- return {
642
- TAG: "Error",
643
- _0: "Could parse string as JSON"
644
- };
645
- }
646
- return fromJSON(parsed);
647
- }
648
- try {
649
- return fromJSON(any);
650
- }
651
- catch (exn$1){
652
- return {
653
- TAG: "Error",
654
- _0: "Could not deserialize the data"
655
- };
656
- }
657
- };
658
637
  return {
659
638
  name: name,
660
639
  fromJSON: fromJSON,
661
- fromAny: fromAny,
662
640
  checkFieldsSanity: checkFieldsSanity$1
663
641
  };
664
642
  }
@@ -234,11 +234,7 @@ Qunit.module("Recursive deserializer", (function (param) {
234
234
  "records",
235
235
  {
236
236
  TAG: "Deserializer",
237
- _0: {
238
- name: List.name,
239
- fromJSON: List.fromJSON,
240
- checkFieldsSanity: List.checkFieldsSanity
241
- }
237
+ _0: List
242
238
  }
243
239
  ],
244
240
  [
@@ -111,11 +111,11 @@ function toString(type_) {
111
111
  case "Array" :
112
112
  return "Array of " + toString(type_._0);
113
113
  case "Tuple" :
114
- return "Tuple of (" + Prelude.$$Array.map(type_._0, toString).join(", ") + ")";
114
+ return "Tuple of (" + type_._0.map(toString).join(", ") + ")";
115
115
  case "Object" :
116
- var desc = Prelude.$$Array.map(type_._0, (function (param) {
117
- return param[0] + ": " + toString(param[1]);
118
- })).join(", ");
116
+ var desc = type_._0.map(function (param) {
117
+ return param[0] + ": " + toString(param[1]);
118
+ }).join(", ");
119
119
  return "Object of {" + desc + "}";
120
120
  case "Optional" :
121
121
  case "OptionalWithDefault" :
@@ -307,7 +307,7 @@ function fromUntagged(untagged, _shape, self) {
307
307
  } else {
308
308
  if (match.TAG === "JSONArray") {
309
309
  var shape$1 = shape._0;
310
- return Prelude.$$Array.map(match._0, (function(shape$1){
310
+ return match._0.map((function(shape$1){
311
311
  return function (item) {
312
312
  return fromUntagged(item, shape$1, self);
313
313
  }
@@ -345,7 +345,7 @@ function fromUntagged(untagged, _shape, self) {
345
345
  } else {
346
346
  if (match.TAG === "JSONObject") {
347
347
  var values = match._0;
348
- return Curry._1(Prelude.Dict.fromArray, Prelude.$$Array.map(shape._0, (function(values){
348
+ return Curry._1(Prelude.Dict.fromArray, shape._0.map((function(values){
349
349
  return function (param) {
350
350
  var field = param[0];
351
351
  var value;
@@ -427,9 +427,13 @@ function fromUntagged(untagged, _shape, self) {
427
427
  exit = 1;
428
428
  } else {
429
429
  if (match.TAG === "JSONArray") {
430
- return Prelude.$$Array.map(Prelude.$$Array.keepSome(Prelude.$$Array.map(Prelude.$$Array.map(match._0, shape._0.fromJSON), Prelude.Result.warn)), (function (prim) {
431
- return prim;
432
- }));
430
+ return Prelude.$$Array.keepSome(match._0.map((function(shape){
431
+ return function (param) {
432
+ return Curry._1(shape._0.fromJSON, param);
433
+ }
434
+ }(shape))).map(Prelude.Result.warn)).map(function (prim) {
435
+ return prim;
436
+ });
433
437
  }
434
438
  exit = 1;
435
439
  }
@@ -519,7 +523,7 @@ function checkFieldsSanity(name, _fields, _optional) {
519
523
  } else {
520
524
  switch (fields.TAG) {
521
525
  case "Tuple" :
522
- return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(Prelude.$$Array.mapWithIndex(fields._0, (function(optional){
526
+ return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
523
527
  return function (field, index) {
524
528
  return function () {
525
529
  return checkFieldsSanity(name + "[" + String(index) + "]", field, optional);
@@ -529,7 +533,7 @@ function checkFieldsSanity(name, _fields, _optional) {
529
533
 
530
534
  }));
531
535
  case "Object" :
532
- return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(Prelude.$$Array.map(fields._0, (function(optional){
536
+ return Curry._2(Prelude.ManyResults.map, Prelude.ManyResults.bailU(fields._0.map((function(optional){
533
537
  return function (param) {
534
538
  var field = param[1];
535
539
  var fieldName = param[0];
@@ -607,7 +611,7 @@ var Field = {
607
611
 
608
612
  function MakeDeserializer(S) {
609
613
  var fields = S.fields;
610
- var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line 329, characters 39-47";
614
+ var name = "Deserializer " + "JSON" + ", " + "File \"JSON.res\", line 320, characters 39-47";
611
615
  var checkFieldsSanity$1 = function () {
612
616
  return checkFieldsSanity(name, fields, false);
613
617
  };
@@ -631,35 +635,9 @@ function MakeDeserializer(S) {
631
635
  _0: res
632
636
  };
633
637
  };
634
- var fromAny = function (any) {
635
- var match = typeof any;
636
- if (match === "string") {
637
- var parsed;
638
- try {
639
- parsed = JSON.parse(any);
640
- }
641
- catch (exn){
642
- return {
643
- TAG: "Error",
644
- _0: "Could parse string as JSON"
645
- };
646
- }
647
- return fromJSON(parsed);
648
- }
649
- try {
650
- return fromJSON(any);
651
- }
652
- catch (exn$1){
653
- return {
654
- TAG: "Error",
655
- _0: "Could not deserialize the data"
656
- };
657
- }
658
- };
659
638
  return {
660
639
  name: name,
661
640
  fromJSON: fromJSON,
662
- fromAny: fromAny,
663
641
  checkFieldsSanity: checkFieldsSanity$1
664
642
  };
665
643
  }
@@ -235,11 +235,7 @@ Qunit.module("Recursive deserializer", (function (param) {
235
235
  "records",
236
236
  {
237
237
  TAG: "Deserializer",
238
- _0: {
239
- name: List.name,
240
- fromJSON: List.fromJSON,
241
- checkFieldsSanity: List.checkFieldsSanity
242
- }
238
+ _0: List
243
239
  }
244
240
  ],
245
241
  [
@@ -0,0 +1,2 @@
1
+ #Start(1700763494807)
2
+ #Done(1700763494809)
@@ -0,0 +1,7 @@
1
+ # ninja log v6
2
+ 0 3 1700763106533253326 JSON.cmi bafb789a597a23fb
3
+ 0 3 1700763106533253326 JSON.cmj e0df0c7e15c09f8e
4
+ 0 3 1700763106533253326 JSON.cmt cb51be205f02087e
5
+ 1 3 1700763106533253326 JSON.res b4c17270ea3dc72d
6
+ 3 4 1700763106536586783 install.stamp cff5a5b4c02d30cf
7
+ 0 2 1700763494803628812 JSON.cmt cb51be205f02087e
Binary file
Binary file
Binary file
@@ -0,0 +1,343 @@
1
+ @@uncurried
2
+ open Prelude
3
+
4
+ module FieldValue = {
5
+ type t
6
+ external string: string => t = "%identity"
7
+ external int: int => t = "%identity"
8
+ external float: float => t = "%identity"
9
+ external boolean: bool => t = "%identity"
10
+ external array: array<t> => t = "%identity"
11
+ external object: Dict.t<t> => t = "%identity"
12
+ external mapping: Dict.t<t> => t = "%identity"
13
+ external any: 'a => t = "%identity"
14
+ @val external null: t = "undefined"
15
+
16
+ external asString: t => string = "%identity"
17
+ external asInt: t => int = "%identity"
18
+ external asFloat: t => float = "%identity"
19
+ external asBoolean: t => bool = "%identity"
20
+ external asArray: t => array<'a> = "%identity"
21
+ external asObject: t => 'a = "%identity"
22
+ }
23
+
24
+ exception TypeError(string)
25
+
26
+ @doc("The module type of a built deserializer which is suitable to add as a subparser.")
27
+ module type Deserializer = {
28
+ type t
29
+ let name: string
30
+ let fromJSON: Js.Json.t => result<t, string>
31
+ let checkFieldsSanity: unit => result<unit, string>
32
+ }
33
+
34
+ module Field = {
35
+ type rec t =
36
+ | Any
37
+ | String
38
+ | Literal(string)
39
+ | Int
40
+ | Float
41
+ | Boolean
42
+ | Array(t)
43
+ /// These SHOULD strings in ISO format, but we only validate the string
44
+ /// can be represented in Js.Date without spewing NaN all over the place;
45
+ /// Js.Date.fromString("xxx") returns an object that is mostly unusable.
46
+ ///
47
+ /// We also allow floats and then use Js.Date.fromFloat.
48
+ | Date
49
+ | Datetime // alias of Date
50
+
51
+ | Tuple(array<t>)
52
+ | Object(array<(string, t)>)
53
+ | Optional(t)
54
+ | OptionalWithDefault(t, FieldValue.t)
55
+ /// An arbitrary mapping from names to other arbitrary fields. The
56
+ /// difference with Object, is that you don't know the names of the
57
+ /// expected entries.
58
+ | Mapping(t)
59
+
60
+ | Deserializer(module(Deserializer))
61
+
62
+ /// A specialized Array of deserialized items that ignores unparsable
63
+ /// items and returns the valid collection. This saves the user from
64
+ /// writing 'Array(DefaultWhenInvalid(Optional(Deserializer(module(M)))))'
65
+ /// and then post-process the list of items with 'Array.keepSome'
66
+ | Collection(module(Deserializer))
67
+ | DefaultWhenInvalid(t, FieldValue.t)
68
+
69
+ // FIXME: this is used to add additional restrictions like variadictInt or
70
+ // variadicString; but I find it too type-unsafe. I might consider having
71
+ // a Constraints for this in the future.
72
+ | Morphism(t, FieldValue.t => FieldValue.t)
73
+
74
+ | Self
75
+
76
+ let usingString = (f: string => 'a) => value => value->FieldValue.asString->f->FieldValue.any
77
+ let usingInt = (f: int => 'a) => value => value->FieldValue.asInt->f->FieldValue.any
78
+ let usingFloat = (f: float => 'a) => value => value->FieldValue.asFloat->f->FieldValue.any
79
+ let usingBoolean = (f: bool => 'a) => value => value->FieldValue.asBoolean->f->FieldValue.any
80
+ let usingArray = (f: array<'a> => 'b) => value => value->FieldValue.asArray->f->FieldValue.any
81
+ let usingObject = (f: 'a => 'b) => value => value->FieldValue.asObject->f->FieldValue.any
82
+
83
+ let variadicInt = (hint: string, fromJs: int => option<'variadicType>) => Morphism(
84
+ Int,
85
+ usingInt(i => {
86
+ switch i->fromJs {
87
+ | Some(internalValue) => internalValue
88
+ | None =>
89
+ raise(TypeError(`This Int(${i->Int.toString}) not a valid value here. Hint: ${hint}`))
90
+ }
91
+ }),
92
+ )
93
+ let variadicString = (hint: string, fromJs: string => option<'variadicType>) => Morphism(
94
+ String,
95
+ usingString(i => {
96
+ switch i->fromJs {
97
+ | Some(internalValue) => internalValue
98
+ | None => raise(TypeError(`This String("${i}") not a valid value here. Hint: ${hint}`))
99
+ }
100
+ }),
101
+ )
102
+
103
+ let rec toString = (type_: t) =>
104
+ switch type_ {
105
+ | Any => "Any"
106
+ | String => "String"
107
+ | Literal(lit) => `Literal: ${lit}`
108
+ | Int => "Integer"
109
+ | Float => "Float"
110
+ | Boolean => "Boolean"
111
+ | Datetime
112
+ | Date => "Date"
113
+ | Self => "Self (recursive)"
114
+ | Collection(m) => {
115
+ module M = unpack(m: Deserializer)
116
+ "Collection of " ++ M.name
117
+ }
118
+
119
+ | Array(t) => "Array of " ++ t->toString
120
+ | Tuple(bases) => `Tuple of (${bases->Array.map(toString)->Array.join(", ")})`
121
+ | Object(fields) => {
122
+ let desc = fields->Array.map(((field, t)) => `${field}: ${t->toString}`)->Array.join(", ")
123
+ `Object of {${desc}}`
124
+ }
125
+
126
+ | OptionalWithDefault(t, _)
127
+ | Optional(t) =>
128
+ "Null of " ++ t->toString
129
+ | Mapping(t) => `Mapping of ${t->toString}`
130
+ | Morphism(t, _) => t->toString ++ " to apply a morphism"
131
+ | Deserializer(m) => {
132
+ module M = unpack(m: Deserializer)
133
+ M.name
134
+ }
135
+
136
+ | DefaultWhenInvalid(t, _) => `Protected ${t->toString}`
137
+ }
138
+
139
+ let _taggedToString = (tagged: Js.Json.tagged_t) => {
140
+ switch tagged {
141
+ | Js.Json.JSONFalse => "Boolean(false)"
142
+ | Js.Json.JSONTrue => "Boolean(true)"
143
+ | Js.Json.JSONNull => "Null"
144
+ | Js.Json.JSONString(text) => `String("${text}")`
145
+ | Js.Json.JSONNumber(number) => `Number(${number->Float.toString})`
146
+ | Js.Json.JSONObject(obj) => `Object(${obj->Js.Json.stringifyAny->default("...")})`
147
+ | Js.Json.JSONArray(array) => `Array(${array->Js.Json.stringifyAny->default("...")})`
148
+ }
149
+ }
150
+
151
+ let rec extractValue = (
152
+ values: Dict.t<Js.Json.t>,
153
+ field: string,
154
+ shape: t,
155
+ self: t,
156
+ ): FieldValue.t => {
157
+ switch values->Dict.get(field) {
158
+ | Some(value) => value->fromUntagged(shape, self)
159
+ | None =>
160
+ switch shape {
161
+ | DefaultWhenInvalid(_, _) => Js.Json.null->fromUntagged(shape, self)
162
+ | Optional(_) => Js.Json.null->fromUntagged(shape, self)
163
+ | OptionalWithDefault(_, default) => default
164
+ | _ => raise(TypeError(`Missing non-optional field '${field}'`))
165
+ }
166
+ }
167
+ }
168
+ and fromUntagged = (untagged: Js.Json.t, shape: t, self: t): FieldValue.t => {
169
+ switch (shape, untagged->Js.Json.classify) {
170
+ | (Any, _) => untagged->FieldValue.any
171
+ | (Literal(expected), Js.Json.JSONString(text)) =>
172
+ if text == expected {
173
+ FieldValue.string(text)
174
+ } else {
175
+ raise(TypeError(`Expecting literal ${expected}, got ${text}`))
176
+ }
177
+ | (String, Js.Json.JSONString(text)) => FieldValue.string(text)
178
+ | (Int, Js.Json.JSONNumber(number)) => FieldValue.int(number->Float.toInt)
179
+ | (Float, Js.Json.JSONNumber(number)) => FieldValue.float(number)
180
+ | (Boolean, Js.Json.JSONTrue) => FieldValue.boolean(true)
181
+ | (Boolean, Js.Json.JSONFalse) => FieldValue.boolean(false)
182
+ | (Tuple(bases), Js.Json.JSONArray(items)) => {
183
+ let lenbases = bases->Array.length
184
+ let lenitems = items->Array.length
185
+ if lenbases == lenitems {
186
+ let values = Array.zipBy(items, bases, (i, b) => fromUntagged(i, b, self))
187
+ values->FieldValue.array
188
+ } else {
189
+ raise(
190
+ TypeError(`Expecting ${lenbases->Int.toString} items, got ${lenitems->Int.toString}`),
191
+ )
192
+ }
193
+ }
194
+
195
+ | (Datetime, Js.Json.JSONString(s))
196
+ | (Date, Js.Json.JSONString(s)) => {
197
+ let r = Js.Date.fromString(s)
198
+ if r->Js.Date.getDate->Js.Float.isNaN {
199
+ raise(TypeError(`Invalid date ${s}`))
200
+ }
201
+ r->FieldValue.any
202
+ }
203
+
204
+ | (Datetime, Js.Json.JSONNumber(f))
205
+ | (Date, Js.Json.JSONNumber(f)) => {
206
+ let r = Js.Date.fromFloat(f)
207
+ if r->Js.Date.getDate->Js.Float.isNaN {
208
+ raise(TypeError(`Invalid date ${f->Js.Float.toString}`))
209
+ }
210
+ r->FieldValue.any
211
+ }
212
+
213
+ | (Array(shape), Js.Json.JSONArray(items)) =>
214
+ FieldValue.array(items->Array.map(item => item->fromUntagged(shape, self)))
215
+ | (Mapping(f), Js.Json.JSONObject(values)) =>
216
+ values->Dict.mapValues(v => v->fromUntagged(f, self))->FieldValue.mapping
217
+ | (Object(fields), Js.Json.JSONObject(values)) =>
218
+ FieldValue.object(
219
+ fields
220
+ ->Array.map(((field, shape)) => {
221
+ let value = switch extractValue(values, field, shape, self) {
222
+ | value => value
223
+ | exception TypeError(msg) => raise(TypeError(`Field "${field}": ${msg}`))
224
+ }
225
+ (field, value)
226
+ })
227
+ ->Dict.fromArray,
228
+ )
229
+
230
+ | (OptionalWithDefault(_, value), Js.Json.JSONNull) => value
231
+ | (OptionalWithDefault(shape, _), _) => untagged->fromUntagged(shape, self)
232
+ | (Optional(_), Js.Json.JSONNull) => FieldValue.null
233
+ | (Optional(shape), _) => untagged->fromUntagged(shape, self)
234
+ | (Morphism(shape, f), _) => untagged->fromUntagged(shape, self)->f->FieldValue.any
235
+
236
+ | (Collection(m), Js.Json.JSONArray(items)) => {
237
+ module M = unpack(m: Deserializer)
238
+ items
239
+ ->Array.map(M.fromJSON)
240
+ ->Array.map(Result.warn)
241
+ ->Array.keepSome
242
+ ->Array.map(FieldValue.any)
243
+ ->FieldValue.array
244
+ }
245
+
246
+ | (Deserializer(m), _) => {
247
+ module M = unpack(m: Deserializer)
248
+ switch untagged->M.fromJSON {
249
+ | Ok(res) => res->FieldValue.any
250
+ | Error(msg) => raise(TypeError(msg))
251
+ }
252
+ }
253
+
254
+ | (DefaultWhenInvalid(t, default), _) =>
255
+ switch untagged->fromUntagged(t, self) {
256
+ | res => res
257
+ | exception TypeError(msg) => {
258
+ Js.Console.warn2("Detected and ignore (with default): ", msg)
259
+ default
260
+ }
261
+ }
262
+ | (Self, _) => untagged->fromUntagged(self, self)
263
+ | (expected, actual) =>
264
+ raise(TypeError(`Expected ${expected->toString}, but got ${actual->_taggedToString} instead`))
265
+ }
266
+ }
267
+
268
+ let rec checkFieldsSanity = (name: string, fields: t, optional: bool): result<unit, string> =>
269
+ switch (fields, optional) {
270
+ | (Self, false) => Error(`${name}: Trivial infinite recursion 'let fields = Self'`)
271
+ | (Self, true) => Ok()
272
+
273
+ | (Any, _) => Ok()
274
+ | (String, _) | (Float, _) | (Int, _) | (Literal(_), _) => Ok()
275
+ | (Boolean, _) | (Date, _) | (Datetime, _) => Ok()
276
+ | (Morphism(_, _), _) => Ok()
277
+
278
+ | (Collection(mod), _)
279
+ | (Deserializer(mod), _) => {
280
+ module M = unpack(mod: Deserializer)
281
+ switch M.checkFieldsSanity() {
282
+ | Ok() => Ok()
283
+ | Error(msg) => Error(`${name}/ ${msg}`)
284
+ }
285
+ }
286
+
287
+ | (DefaultWhenInvalid(fields, _), _)
288
+ | (OptionalWithDefault(fields, _), _)
289
+ | (Optional(fields), _) =>
290
+ checkFieldsSanity(name, fields, true)
291
+
292
+ | (Object(fields), optional) =>
293
+ fields
294
+ ->Array.map(((fieldName, field)) => () =>
295
+ checkFieldsSanity(`${name}::${fieldName}`, field, optional))
296
+ ->ManyResults.bailU
297
+ ->ManyResults.map(_ => ())
298
+
299
+ /// Mappings and arrays can be empty, so their payloads are
300
+ /// automatically optional.
301
+ | (Mapping(field), _) | (Array(field), _) => checkFieldsSanity(name, field, true)
302
+
303
+ | (Tuple(fields), optional) =>
304
+ fields
305
+ ->Array.mapWithIndex((field, index) => () =>
306
+ checkFieldsSanity(`${name}[${index->Int.toString}]`, field, optional))
307
+ ->ManyResults.bailU
308
+ ->ManyResults.map(_ => ())
309
+ }
310
+ }
311
+
312
+ module type Serializable = {
313
+ type t
314
+ let fields: Field.t
315
+ }
316
+
317
+ module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) => {
318
+ type t = S.t
319
+ let fields = S.fields
320
+ %%private(let (loc, _f) = __LOC_OF__(module(S: Serializable)))
321
+ let name = `Deserializer ${__MODULE__}, ${loc}`
322
+
323
+ %%private(external _toNativeType: FieldValue.t => t = "%identity")
324
+
325
+ @doc("Checks for trivial infinite-recursion in the fields of the module.
326
+
327
+ Notice this algorithm is just an heuristic, and it might happen that are
328
+ cases of infinite-recursion not detected and cases where detection is a
329
+ false positive.
330
+
331
+ You should use this only while debugging/developing to verify your data.
332
+
333
+ ")
334
+ let checkFieldsSanity = () => Field.checkFieldsSanity(name, fields, false)
335
+
336
+ @doc("Parse a `Js.Json.t` into `result<t, string>`")
337
+ let fromJSON = (json: Js.Json.t): result<t, _> => {
338
+ switch json->Field.fromUntagged(fields, fields) {
339
+ | res => Ok(res->_toNativeType)
340
+ | exception TypeError(e) => Error(e)
341
+ }
342
+ }
343
+ }
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaiko.io/rescript-deser",
3
- "version": "4.0.0-rc.2",
3
+ "version": "4.0.0-rc.4",
4
4
  "keywords": [
5
5
  "json",
6
6
  "deserializer",
@@ -20,7 +20,7 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@kaiko.io/rescript-prelude": "7.0.0-rc.3"
23
+ "@kaiko.io/rescript-prelude": "7.0.0-rc.4"
24
24
  },
25
25
  "devDependencies": {
26
26
  "esbuild": "^0.15.7",
package/src/JSON.res CHANGED
@@ -23,20 +23,11 @@ module FieldValue = {
23
23
 
24
24
  exception TypeError(string)
25
25
 
26
- @doc("The module type of a deserializer which is suitable to add as a subparser.")
27
- module type ArgDeserializer = {
28
- type t
29
- let name: string
30
- let fromJSON: Js.Json.t => result<t, string>
31
- let checkFieldsSanity: unit => result<unit, string>
32
- }
33
-
34
- @doc("The module type of a built deserializer.")
26
+ @doc("The module type of a built deserializer which is suitable to add as a subparser.")
35
27
  module type Deserializer = {
36
28
  type t
37
29
  let name: string
38
30
  let fromJSON: Js.Json.t => result<t, string>
39
- let fromAny: 'a => result<t, string>
40
31
  let checkFieldsSanity: unit => result<unit, string>
41
32
  }
42
33
 
@@ -66,13 +57,13 @@ module Field = {
66
57
  /// expected entries.
67
58
  | Mapping(t)
68
59
 
69
- | Deserializer(module(ArgDeserializer))
60
+ | Deserializer(module(Deserializer))
70
61
 
71
62
  /// A specialized Array of deserialized items that ignores unparsable
72
63
  /// items and returns the valid collection. This saves the user from
73
64
  /// writing 'Array(DefaultWhenInvalid(Optional(Deserializer(module(M)))))'
74
65
  /// and then post-process the list of items with 'Array.keepSome'
75
- | Collection(module(ArgDeserializer))
66
+ | Collection(module(Deserializer))
76
67
  | DefaultWhenInvalid(t, FieldValue.t)
77
68
 
78
69
  // FIXME: this is used to add additional restrictions like variadictInt or
@@ -121,7 +112,7 @@ module Field = {
121
112
  | Date => "Date"
122
113
  | Self => "Self (recursive)"
123
114
  | Collection(m) => {
124
- module M = unpack(m: ArgDeserializer)
115
+ module M = unpack(m: Deserializer)
125
116
  "Collection of " ++ M.name
126
117
  }
127
118
 
@@ -138,7 +129,7 @@ module Field = {
138
129
  | Mapping(t) => `Mapping of ${t->toString}`
139
130
  | Morphism(t, _) => t->toString ++ " to apply a morphism"
140
131
  | Deserializer(m) => {
141
- module M = unpack(m: ArgDeserializer)
132
+ module M = unpack(m: Deserializer)
142
133
  M.name
143
134
  }
144
135
 
@@ -243,7 +234,7 @@ module Field = {
243
234
  | (Morphism(shape, f), _) => untagged->fromUntagged(shape, self)->f->FieldValue.any
244
235
 
245
236
  | (Collection(m), Js.Json.JSONArray(items)) => {
246
- module M = unpack(m: ArgDeserializer)
237
+ module M = unpack(m: Deserializer)
247
238
  items
248
239
  ->Array.map(M.fromJSON)
249
240
  ->Array.map(Result.warn)
@@ -253,7 +244,7 @@ module Field = {
253
244
  }
254
245
 
255
246
  | (Deserializer(m), _) => {
256
- module M = unpack(m: ArgDeserializer)
247
+ module M = unpack(m: Deserializer)
257
248
  switch untagged->M.fromJSON {
258
249
  | Ok(res) => res->FieldValue.any
259
250
  | Error(msg) => raise(TypeError(msg))
@@ -286,7 +277,7 @@ module Field = {
286
277
 
287
278
  | (Collection(mod), _)
288
279
  | (Deserializer(mod), _) => {
289
- module M = unpack(mod: ArgDeserializer)
280
+ module M = unpack(mod: Deserializer)
290
281
  switch M.checkFieldsSanity() {
291
282
  | Ok() => Ok()
292
283
  | Error(msg) => Error(`${name}/ ${msg}`)
@@ -329,10 +320,7 @@ module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) =>
329
320
  %%private(let (loc, _f) = __LOC_OF__(module(S: Serializable)))
330
321
  let name = `Deserializer ${__MODULE__}, ${loc}`
331
322
 
332
- %%private(
333
- external _toNativeType: FieldValue.t => t = "%identity"
334
- external cast: 'a => 'b = "%identity"
335
- )
323
+ %%private(external _toNativeType: FieldValue.t => t = "%identity")
336
324
 
337
325
  @doc("Checks for trivial infinite-recursion in the fields of the module.
338
326
 
@@ -352,20 +340,4 @@ module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) =>
352
340
  | exception TypeError(e) => Error(e)
353
341
  }
354
342
  }
355
-
356
- @doc("Try to parse anything")
357
- let fromAny = any => {
358
- switch Js.typeof(any) {
359
- | "string" =>
360
- switch any->cast->Js.Json.parseExn {
361
- | parsed => fromJSON(parsed)
362
- | exception _ => Error("Could parse string as JSON")
363
- }
364
- | _ =>
365
- switch any->cast->fromJSON {
366
- | result => result
367
- | exception _ => Error("Could not deserialize the data")
368
- }
369
- }
370
- }
371
343
  }