@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.
- package/README.md +3 -3
- package/lib/bs/.bsdeps +4 -4
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/.ninja_log +55 -43
- package/lib/bs/.project-files-cache +0 -0
- package/lib/bs/___incremental/Deser.cmi +0 -0
- package/lib/bs/___incremental/Deser.cmj +0 -0
- package/lib/bs/___incremental/Deser.cmt +0 -0
- package/lib/bs/___incremental/Deser.res +351 -0
- package/lib/bs/___incremental/index.cmi +0 -0
- package/lib/bs/___incremental/index.cmj +0 -0
- package/lib/bs/___incremental/index.cmt +0 -0
- package/lib/bs/___incremental/index.res +189 -0
- package/lib/bs/src/Deser.ast +0 -0
- package/lib/bs/src/Deser.cmi +0 -0
- package/lib/bs/src/Deser.cmj +0 -0
- package/lib/bs/src/Deser.cmt +0 -0
- package/lib/bs/tests/QUnit.cmt +0 -0
- package/lib/bs/tests/index.ast +0 -0
- package/lib/bs/tests/index.cmi +0 -0
- package/lib/bs/tests/index.cmt +0 -0
- package/lib/es6/src/Deser.js +133 -237
- package/lib/js/src/Deser.js +133 -237
- package/package.json +2 -1
- package/src/Deser.res +80 -74
- package/tests/index.res +10 -10
- package/tests/run-tests.js +192 -0
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ The resultant module has type:
|
|
|
50
50
|
module type Deserializer = {
|
|
51
51
|
type t
|
|
52
52
|
let name: string
|
|
53
|
-
let fromJSON:
|
|
53
|
+
let fromJSON: RescriptCore.JSON.t => result<t, string>
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -80,10 +80,10 @@ module type Serializable = {
|
|
|
80
80
|
JS representation of the object that comes from the JSON data.
|
|
81
81
|
|
|
82
82
|
- `Date`, parses either a string representation of a date (datetime) or a
|
|
83
|
-
floating point representation of date (datetime) into `
|
|
83
|
+
floating point representation of date (datetime) into `Date.t`; we make
|
|
84
84
|
sure the result is valid and won't return NaN afterwards.
|
|
85
85
|
|
|
86
|
-
This basically calls, `
|
|
86
|
+
This basically calls, `Date.fromString` or `Date.fromTime`; and tests
|
|
87
87
|
the resulting value.
|
|
88
88
|
|
|
89
89
|
`Datetime` is an alias for `Date`.
|
package/lib/bs/.bsdeps
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/home/manu/src/kaiko/rescript-deser
|
|
3
3
|
0
|
|
4
4
|
0
|
|
5
|
-
rescript.json 0x1.
|
|
6
|
-
tests 0x1.
|
|
7
|
-
src/ 0x1.
|
|
5
|
+
rescript.json 0x1.a0f2462508cf1p+30
|
|
6
|
+
tests 0x1.a472319d551bdp+30
|
|
7
|
+
src/ 0x1.a4721291940c2p+30
|
|
8
8
|
===
|
|
9
|
-
/home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/rescript.exe 0x1.
|
|
9
|
+
/home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/rescript.exe 0x1.a4720d14765c9p+30
|
package/lib/bs/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1763479665497)
|
|
2
|
+
#Done(1763479665497)
|
package/lib/bs/.ninja_log
CHANGED
|
@@ -1,45 +1,57 @@
|
|
|
1
1
|
# ninja log v6
|
|
2
|
-
1
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
1 18 1732888969609093921 tests/QUnit.cmi f57b53b8aff27629
|
|
3
|
+
82 114 1763477825207250407 ../es6/tests/index.js fe0b84000747f3c8
|
|
4
|
+
82 114 1763477825207250407 tests/index.cmj fe0b84000747f3c8
|
|
5
|
+
6 8 1748799957991823370 tests/index.d 44655d60e4c70d26
|
|
6
|
+
23 82 1763477825165250216 ../es6/src/Deser.js 9301f060e01e8bd6
|
|
5
7
|
9 11 1732888969609093921 tests/QUnit.d f74946ee3200f9fa
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
1
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
8
|
+
1 18 1732888969609093921 ../es6/tests/QUnit.js f57b53b8aff27629
|
|
9
|
+
23 82 1763477825165250216 src/Deser.cmj 9301f060e01e8bd6
|
|
10
|
+
1 18 1732888969609093921 ../js/tests/QUnit.js f57b53b8aff27629
|
|
11
|
+
23 82 1763477825165250216 src/Deser.cmi 9301f060e01e8bd6
|
|
12
|
+
1 17 1763477825165250216 src/Deser.ast 60777bbe51986b3b
|
|
13
|
+
82 114 1763477825207250407 ../js/tests/index.js fe0b84000747f3c8
|
|
14
|
+
82 114 1763477825207250407 tests/index.cmi fe0b84000747f3c8
|
|
15
|
+
23 82 1763477825165250216 ../js/src/Deser.js 9301f060e01e8bd6
|
|
16
|
+
1 18 1732888969609093921 tests/QUnit.cmj f57b53b8aff27629
|
|
17
|
+
18 23 1763477825165250216 src/Deser.d 691c816990b589e8
|
|
18
|
+
0 9 1732888969605760485 tests/QUnit.ast 24679400da87e6b0
|
|
19
|
+
0 6 1748799957991823370 tests/index.ast 901a254e17150891
|
|
20
|
+
0 14 1732888969609093921 tests/QUnit.cmj d4cd0d743e9dbdc2
|
|
21
|
+
0 14 1732888969609093921 tests/QUnit.cmi d4cd0d743e9dbdc2
|
|
22
|
+
0 14 1732888969609093921 ../es6/tests/QUnit.js d4cd0d743e9dbdc2
|
|
23
|
+
0 14 1732888969609093921 ../js/tests/QUnit.js d4cd0d743e9dbdc2
|
|
24
|
+
1 44 1763477825165250216 src/Deser.cmj d1ae51e3ae7660c0
|
|
25
|
+
1 44 1763477825165250216 src/Deser.cmi d1ae51e3ae7660c0
|
|
26
|
+
1 44 1763477825165250216 ../es6/src/Deser.js d1ae51e3ae7660c0
|
|
27
|
+
1 44 1763477825165250216 ../js/src/Deser.js d1ae51e3ae7660c0
|
|
28
|
+
44 66 1763477825207250407 tests/index.cmj d34384b01ad99c82
|
|
29
|
+
44 66 1763477825207250407 tests/index.cmi d34384b01ad99c82
|
|
30
|
+
44 66 1763477825207250407 ../es6/tests/index.js d34384b01ad99c82
|
|
31
|
+
44 66 1763477825207250407 ../js/tests/index.js d34384b01ad99c82
|
|
32
|
+
0 14 1763477953358547914 tests/index.ast 901a254e17150891
|
|
33
|
+
14 18 1763477953358547914 tests/index.d 44655d60e4c70d26
|
|
34
|
+
0 19 1732888969609093921 tests/QUnit.cmj eee74ca8670f321f
|
|
35
|
+
0 19 1732888969609093921 tests/QUnit.cmi eee74ca8670f321f
|
|
36
|
+
0 19 1732888969609093921 ../es6/tests/QUnit.js eee74ca8670f321f
|
|
37
|
+
0 19 1732888969609093921 ../js/tests/QUnit.js eee74ca8670f321f
|
|
38
|
+
0 55 1763477825165250216 src/Deser.cmj 53951e70cb585c7f
|
|
39
|
+
0 55 1763477825165250216 src/Deser.cmi 53951e70cb585c7f
|
|
40
|
+
0 55 1763477825165250216 ../es6/src/Deser.js 53951e70cb585c7f
|
|
41
|
+
0 55 1763477825165250216 ../js/src/Deser.js 53951e70cb585c7f
|
|
42
|
+
55 82 1763477953358547914 tests/index.cmj 3635592f4bb018fd
|
|
43
|
+
55 82 1763477953358547914 tests/index.cmi 3635592f4bb018fd
|
|
44
|
+
55 82 1763477953358547914 ../es6/tests/index.js 3635592f4bb018fd
|
|
45
|
+
55 82 1763477953358547914 ../js/tests/index.js 3635592f4bb018fd
|
|
46
|
+
0 15 1732888969609093921 tests/QUnit.cmj 808e7abe2ed23b68
|
|
47
|
+
0 15 1732888969609093921 tests/QUnit.cmi 808e7abe2ed23b68
|
|
48
|
+
0 15 1732888969609093921 ../es6/tests/QUnit.js 808e7abe2ed23b68
|
|
49
|
+
0 15 1732888969609093921 ../js/tests/QUnit.js 808e7abe2ed23b68
|
|
50
|
+
0 43 1763477825165250216 src/Deser.cmj d15a15aaf540f200
|
|
51
|
+
0 43 1763477825165250216 src/Deser.cmi d15a15aaf540f200
|
|
52
|
+
0 43 1763477825165250216 ../es6/src/Deser.js d15a15aaf540f200
|
|
53
|
+
0 43 1763477825165250216 ../js/src/Deser.js d15a15aaf540f200
|
|
54
|
+
43 65 1763477953358547914 tests/index.cmj 6ccb61e0c25bd9c7
|
|
55
|
+
43 65 1763477953358547914 tests/index.cmi 6ccb61e0c25bd9c7
|
|
56
|
+
43 65 1763477953358547914 ../es6/tests/index.js 6ccb61e0c25bd9c7
|
|
57
|
+
43 65 1763477953358547914 ../js/tests/index.js 6ccb61e0c25bd9c7
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
open RescriptCore
|
|
2
|
+
|
|
3
|
+
module FieldValue = {
|
|
4
|
+
type t
|
|
5
|
+
external string: string => t = "%identity"
|
|
6
|
+
external int: int => t = "%identity"
|
|
7
|
+
external float: float => t = "%identity"
|
|
8
|
+
external boolean: bool => t = "%identity"
|
|
9
|
+
external array: array<t> => t = "%identity"
|
|
10
|
+
external object: Dict.t<t> => t = "%identity"
|
|
11
|
+
external mapping: Dict.t<t> => t = "%identity"
|
|
12
|
+
external any: 'a => t = "%identity"
|
|
13
|
+
@val external null: t = "undefined"
|
|
14
|
+
|
|
15
|
+
external asString: t => string = "%identity"
|
|
16
|
+
external asInt: t => int = "%identity"
|
|
17
|
+
external asFloat: t => float = "%identity"
|
|
18
|
+
external asBoolean: t => bool = "%identity"
|
|
19
|
+
external asArray: t => array<'a> = "%identity"
|
|
20
|
+
external asObject: t => 'a = "%identity"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exception TypeError(string)
|
|
24
|
+
|
|
25
|
+
@doc("The module type of a built deserializer which is suitable to add as a subparser.")
|
|
26
|
+
module type Deserializer = {
|
|
27
|
+
type t
|
|
28
|
+
let name: string
|
|
29
|
+
let fromJSON: JSON.t => result<t, string>
|
|
30
|
+
let checkFieldsSanity: unit => result<unit, string>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module Field = {
|
|
34
|
+
type rec t =
|
|
35
|
+
| Any
|
|
36
|
+
| String
|
|
37
|
+
| Literal(string)
|
|
38
|
+
| Int
|
|
39
|
+
| Float
|
|
40
|
+
| Boolean
|
|
41
|
+
| Array(t)
|
|
42
|
+
// These SHOULD strings in ISO format, but we only validate the string
|
|
43
|
+
// can be represented in Js.Date without spewing NaN all over the place;
|
|
44
|
+
// Js.Date.fromString("xxx") returns an object that is mostly unusable.
|
|
45
|
+
///
|
|
46
|
+
// We also allow floats and then use Date.fromTime
|
|
47
|
+
| Date
|
|
48
|
+
| Datetime // alias of Date
|
|
49
|
+
|
|
50
|
+
| Tuple(array<t>)
|
|
51
|
+
| Object(array<(string, t)>)
|
|
52
|
+
| Optional(t)
|
|
53
|
+
| OptionalWithDefault(t, FieldValue.t)
|
|
54
|
+
// An arbitrary mapping from names to other arbitrary fields. The
|
|
55
|
+
// difference with Object, is that you don't know the names of the
|
|
56
|
+
// expected entries.
|
|
57
|
+
| Mapping(t)
|
|
58
|
+
|
|
59
|
+
| Deserializer(module(Deserializer))
|
|
60
|
+
|
|
61
|
+
// A specialized Array of deserialized items that ignores unparsable
|
|
62
|
+
// items and returns the valid collection. This saves the user from
|
|
63
|
+
// writing 'Array(DefaultWhenInvalid(Optional(Deserializer(module(M)))))'
|
|
64
|
+
// and then post-process the list of items with 'Array.keepSome'
|
|
65
|
+
| Collection(module(Deserializer))
|
|
66
|
+
| DefaultWhenInvalid(t, FieldValue.t)
|
|
67
|
+
|
|
68
|
+
// FIXME: this is used to add additional restrictions like variadictInt or
|
|
69
|
+
// variadicString; but I find it too type-unsafe. I might consider having
|
|
70
|
+
// a Constraints for this in the future.
|
|
71
|
+
| Morphism(t, FieldValue.t => FieldValue.t)
|
|
72
|
+
|
|
73
|
+
| Self
|
|
74
|
+
|
|
75
|
+
let usingString = (f: string => 'a) => value => value->FieldValue.asString->f->FieldValue.any
|
|
76
|
+
let usingInt = (f: int => 'a) => value => value->FieldValue.asInt->f->FieldValue.any
|
|
77
|
+
let usingFloat = (f: float => 'a) => value => value->FieldValue.asFloat->f->FieldValue.any
|
|
78
|
+
let usingBoolean = (f: bool => 'a) => value => value->FieldValue.asBoolean->f->FieldValue.any
|
|
79
|
+
let usingArray = (f: array<'a> => 'b) => value => value->FieldValue.asArray->f->FieldValue.any
|
|
80
|
+
let usingObject = (f: 'a => 'b) => value => value->FieldValue.asObject->f->FieldValue.any
|
|
81
|
+
|
|
82
|
+
let variadicInt = (hint: string, fromJs: int => option<'variadicType>) => Morphism(
|
|
83
|
+
Int,
|
|
84
|
+
usingInt(i => {
|
|
85
|
+
switch i->fromJs {
|
|
86
|
+
| Some(internalValue) => internalValue
|
|
87
|
+
| None =>
|
|
88
|
+
raise(TypeError(`This Int(${i->Int.toString}) not a valid value here. Hint: ${hint}`))
|
|
89
|
+
}
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
let variadicString = (hint: string, fromJs: string => option<'variadicType>) => Morphism(
|
|
93
|
+
String,
|
|
94
|
+
usingString(i => {
|
|
95
|
+
switch i->fromJs {
|
|
96
|
+
| Some(internalValue) => internalValue
|
|
97
|
+
| None => raise(TypeError(`This String("${i}") not a valid value here. Hint: ${hint}`))
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
let rec toString = (type_: t) =>
|
|
103
|
+
switch type_ {
|
|
104
|
+
| Any => "Any"
|
|
105
|
+
| String => "String"
|
|
106
|
+
| Literal(lit) => `Literal: ${lit}`
|
|
107
|
+
| Int => "Integer"
|
|
108
|
+
| Float => "Float"
|
|
109
|
+
| Boolean => "Boolean"
|
|
110
|
+
| Datetime
|
|
111
|
+
| Date => "Date"
|
|
112
|
+
| Self => "Self (recursive)"
|
|
113
|
+
| Collection(m) => {
|
|
114
|
+
module M = unpack(m: Deserializer)
|
|
115
|
+
"Collection of " ++ M.name
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
| Array(t) => "Array of " ++ t->toString
|
|
119
|
+
| Tuple(bases) => `Tuple of (${bases->Array.map(toString)->Array.join(", ")})`
|
|
120
|
+
| Object(fields) => {
|
|
121
|
+
let desc = fields->Array.map(((field, t)) => `${field}: ${t->toString}`)->Array.join(", ")
|
|
122
|
+
`Object of {${desc}}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
| OptionalWithDefault(t, _)
|
|
126
|
+
| Optional(t) =>
|
|
127
|
+
"Null of " ++ t->toString
|
|
128
|
+
| Mapping(t) => `Mapping of ${t->toString}`
|
|
129
|
+
| Morphism(t, _) => t->toString ++ " to apply a morphism"
|
|
130
|
+
| Deserializer(m) => {
|
|
131
|
+
module M = unpack(m: Deserializer)
|
|
132
|
+
M.name
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
| DefaultWhenInvalid(t, _) => `Protected ${t->toString}`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let _taggedToString = (tagged: JSON.t) => {
|
|
139
|
+
switch tagged {
|
|
140
|
+
| Boolean(false) => "Boolean(false)"
|
|
141
|
+
| Boolean(true) => "Boolean(true)"
|
|
142
|
+
| Null => "Null"
|
|
143
|
+
| String(text) => `String("${text}")`
|
|
144
|
+
| Number(number) => `Number(${number->Float.toString})`
|
|
145
|
+
| Object(obj) => `Object(${obj->JSON.stringifyAny->Option.getOr("...")})`
|
|
146
|
+
| Array(array) => `Array(${array->JSON.stringifyAny->Option.getOr("...")})`
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let rec extractValue = (
|
|
151
|
+
values: Dict.t<JSON.t>,
|
|
152
|
+
field: string,
|
|
153
|
+
shape: t,
|
|
154
|
+
self: t,
|
|
155
|
+
): FieldValue.t => {
|
|
156
|
+
switch values->Dict.get(field) {
|
|
157
|
+
| Some(value) => value->fromUntagged(shape, self)
|
|
158
|
+
| None =>
|
|
159
|
+
switch shape {
|
|
160
|
+
| DefaultWhenInvalid(_, _) => JSON.Null->fromUntagged(shape, self)
|
|
161
|
+
| Optional(_) => JSON.Null->fromUntagged(shape, self)
|
|
162
|
+
| OptionalWithDefault(_, default) => default
|
|
163
|
+
| _ => raise(TypeError(`Missing non-optional field '${field}'`))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
and fromUntagged: (JSON.t, t, t) => FieldValue.t = (untagged, shape, self) => {
|
|
168
|
+
switch (shape, untagged) {
|
|
169
|
+
| (Any, _) => untagged->FieldValue.any
|
|
170
|
+
|
|
171
|
+
| (Literal(expected), String(text)) if text == expected => FieldValue.string(text)
|
|
172
|
+
| (Literal(expected), String(text)) =>
|
|
173
|
+
raise(TypeError(`Expecting literal ${expected}, got ${text}`))
|
|
174
|
+
|
|
175
|
+
| (String, String(text)) => FieldValue.string(text)
|
|
176
|
+
|
|
177
|
+
| (Int, Number(number)) => FieldValue.int(number->Float.toInt)
|
|
178
|
+
|
|
179
|
+
| (Float, Number(number)) => FieldValue.float(number)
|
|
180
|
+
|
|
181
|
+
| (Boolean, Boolean(v)) => FieldValue.boolean(v)
|
|
182
|
+
|
|
183
|
+
| (Tuple(bases), Array(items)) => {
|
|
184
|
+
let lenbases = bases->Array.length
|
|
185
|
+
let lenitems = items->Array.length
|
|
186
|
+
if lenbases == lenitems {
|
|
187
|
+
let values = Belt.Array.zipBy(items, bases, (i, b) => fromUntagged(i, b, self))
|
|
188
|
+
values->FieldValue.array
|
|
189
|
+
} else {
|
|
190
|
+
raise(
|
|
191
|
+
TypeError(`Expecting ${lenbases->Int.toString} items, got ${lenitems->Int.toString}`),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
| (Datetime | Date, String(s)) =>
|
|
197
|
+
let r = Date.fromString(s)
|
|
198
|
+
if r->Date.getDate->Int.toFloat->Float.isNaN {
|
|
199
|
+
raise(TypeError(`Invalid date ${s}`))
|
|
200
|
+
}
|
|
201
|
+
r->FieldValue.any
|
|
202
|
+
|
|
203
|
+
| (Datetime | Date, Number(f)) =>
|
|
204
|
+
let r = Date.fromTime(f)
|
|
205
|
+
if r->Date.getDate->Int.toFloat->Float.isNaN {
|
|
206
|
+
raise(TypeError(`Invalid date ${f->Float.toString}`))
|
|
207
|
+
}
|
|
208
|
+
r->FieldValue.any
|
|
209
|
+
|
|
210
|
+
| (Array(shape), Array(items)) =>
|
|
211
|
+
FieldValue.array(items->Array.map(item => item->fromUntagged(shape, self)))
|
|
212
|
+
|
|
213
|
+
| (Mapping(f), Object(values)) =>
|
|
214
|
+
values->Dict.mapValues(v => v->fromUntagged(f, self))->FieldValue.mapping
|
|
215
|
+
|
|
216
|
+
| (Object(fields), Object(values)) =>
|
|
217
|
+
fields
|
|
218
|
+
->Array.map(((field, shape)) => {
|
|
219
|
+
let value = switch extractValue(values, field, shape, self) {
|
|
220
|
+
| value => value
|
|
221
|
+
| exception TypeError(msg) => raise(TypeError(`Field "${field}": ${msg}`))
|
|
222
|
+
}
|
|
223
|
+
(field, value)
|
|
224
|
+
})
|
|
225
|
+
->Dict.fromArray
|
|
226
|
+
->FieldValue.object
|
|
227
|
+
|
|
228
|
+
| (OptionalWithDefault(_, value), Null) => value
|
|
229
|
+
| (OptionalWithDefault(shape, _), _) => untagged->fromUntagged(shape, self)
|
|
230
|
+
| (Optional(_), Null) => FieldValue.null
|
|
231
|
+
| (Optional(shape), _) => untagged->fromUntagged(shape, self)
|
|
232
|
+
| (Morphism(shape, f), _) => untagged->fromUntagged(shape, self)->f->FieldValue.any
|
|
233
|
+
|
|
234
|
+
| (Collection(m), Array(items)) => {
|
|
235
|
+
module M = unpack(m: Deserializer)
|
|
236
|
+
items
|
|
237
|
+
->Array.map(M.fromJSON)
|
|
238
|
+
->Array.filterMap(x =>
|
|
239
|
+
switch x {
|
|
240
|
+
| Ok(v) => Some(v)
|
|
241
|
+
| Error(msg) =>
|
|
242
|
+
Console.warn3(__MODULE__, "Could not deserialize value in the collection", msg)
|
|
243
|
+
None
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
->Array.map(FieldValue.any)
|
|
247
|
+
->FieldValue.array
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
| (Deserializer(m), _) => {
|
|
251
|
+
module M = unpack(m: Deserializer)
|
|
252
|
+
switch untagged->M.fromJSON {
|
|
253
|
+
| Ok(res) => res->FieldValue.any
|
|
254
|
+
| Error(msg) => raise(TypeError(msg))
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
| (DefaultWhenInvalid(t, default), _) =>
|
|
259
|
+
switch untagged->fromUntagged(t, self) {
|
|
260
|
+
| res => res
|
|
261
|
+
| exception TypeError(msg) => {
|
|
262
|
+
Console.warn2("Detected and ignore (with default): ", msg)
|
|
263
|
+
default
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
| (Self, _) => untagged->fromUntagged(self, self)
|
|
267
|
+
| (expected, actual) =>
|
|
268
|
+
raise(TypeError(`Expected ${expected->toString}, but got ${actual->_taggedToString} instead`))
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let rec checkFieldsSanity = (name: string, fields: t, optional: bool): result<unit, string> =>
|
|
273
|
+
switch (fields, optional) {
|
|
274
|
+
| (Self, false) => Error(`${name}: Trivial infinite recursion 'let fields = Self'`)
|
|
275
|
+
| (Self, true) => Ok()
|
|
276
|
+
|
|
277
|
+
| (Any, _) => Ok()
|
|
278
|
+
| (String, _) | (Float, _) | (Int, _) | (Literal(_), _) => Ok()
|
|
279
|
+
| (Boolean, _) | (Date, _) | (Datetime, _) => Ok()
|
|
280
|
+
| (Morphism(_, _), _) => Ok()
|
|
281
|
+
|
|
282
|
+
| (Collection(mod), _)
|
|
283
|
+
| (Deserializer(mod), _) => {
|
|
284
|
+
module M = unpack(mod: Deserializer)
|
|
285
|
+
switch M.checkFieldsSanity() {
|
|
286
|
+
| Ok() => Ok()
|
|
287
|
+
| Error(msg) => Error(`${name}/ ${msg}`)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
| (DefaultWhenInvalid(fields, _), _)
|
|
292
|
+
| (OptionalWithDefault(fields, _), _)
|
|
293
|
+
| (Optional(fields), _) =>
|
|
294
|
+
checkFieldsSanity(name, fields, true)
|
|
295
|
+
|
|
296
|
+
| (Object(fields), optional) =>
|
|
297
|
+
fields
|
|
298
|
+
->Array.map(((fieldName, field)) => () =>
|
|
299
|
+
checkFieldsSanity(`${name}::${fieldName}`, field, optional))
|
|
300
|
+
->Array.reduce(Ok([]), (res, nextitem) =>
|
|
301
|
+
res->Result.flatMap(arr => nextitem()->Result.map(i => arr->Array.concat([i])))
|
|
302
|
+
)
|
|
303
|
+
->Result.map(_ => ())
|
|
304
|
+
|
|
305
|
+
// Mappings and arrays can be empty, so their payloads are
|
|
306
|
+
// automatically optional.
|
|
307
|
+
| (Mapping(field), _) | (Array(field), _) => checkFieldsSanity(name, field, true)
|
|
308
|
+
|
|
309
|
+
| (Tuple(fields), optional) =>
|
|
310
|
+
fields
|
|
311
|
+
->Array.mapWithIndex((field, index) => () =>
|
|
312
|
+
checkFieldsSanity(`${name}[${index->Int.toString}]`, field, optional))
|
|
313
|
+
->Array.reduce(Ok([]), (res, nextitem) =>
|
|
314
|
+
res->Result.flatMap(arr => nextitem()->Result.map(i => arr->Array.concat([i])))
|
|
315
|
+
)
|
|
316
|
+
->Result.map(_ => ())
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module type Serializable = {
|
|
321
|
+
type t
|
|
322
|
+
let fields: Field.t
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module MakeDeserializer = (S: Serializable): (Deserializer with type t = S.t) => {
|
|
326
|
+
type t = S.t
|
|
327
|
+
let fields = S.fields
|
|
328
|
+
%%private(let (loc, _f) = __LOC_OF__(module(S: Serializable)))
|
|
329
|
+
let name = `Deserializer ${__MODULE__}, ${loc}`
|
|
330
|
+
|
|
331
|
+
%%private(external _toNativeType: FieldValue.t => t = "%identity")
|
|
332
|
+
|
|
333
|
+
@doc("Checks for trivial infinite-recursion in the fields of the module.
|
|
334
|
+
|
|
335
|
+
Notice this algorithm is just an heuristic, and it might happen that are
|
|
336
|
+
cases of infinite-recursion not detected and cases where detection is a
|
|
337
|
+
false positive.
|
|
338
|
+
|
|
339
|
+
You should use this only while debugging/developing to verify your data.
|
|
340
|
+
|
|
341
|
+
")
|
|
342
|
+
let checkFieldsSanity = () => Field.checkFieldsSanity(name, fields, false)
|
|
343
|
+
|
|
344
|
+
@doc("Parse a `'a` into `result<t, string>`")
|
|
345
|
+
let fromJSON: JSON.t => result<t, _> = json => {
|
|
346
|
+
switch json->Field.fromUntagged(fields, fields) {
|
|
347
|
+
| res => Ok(res->_toNativeType)
|
|
348
|
+
| exception TypeError(e) => Error(e)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|