@kaiko.io/rescript-deser 7.0.0-alpha.1 → 7.0.0-alpha.2
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 +13 -2
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/build.ninja +0 -27
- package/lib/bs/compiler-info.json +8 -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/{___incremental → src}/Deser.res +19 -29
- package/lib/bs/tests/QUnit.ast +0 -0
- package/lib/bs/tests/QUnit.cmi +0 -0
- package/lib/bs/tests/QUnit.cmj +0 -0
- package/lib/bs/tests/QUnit.cmt +0 -0
- package/lib/bs/tests/QUnit.res +72 -0
- package/lib/bs/tests/index.ast +0 -0
- package/lib/bs/tests/index.cmi +0 -0
- package/lib/bs/tests/index.cmj +0 -0
- package/lib/bs/tests/index.cmt +0 -0
- package/lib/bs/{___incremental → tests}/index.res +24 -1
- package/lib/es6/src/Deser.js +297 -365
- package/lib/es6/tests/index.js +260 -241
- package/lib/js/src/Deser.js +293 -361
- package/lib/js/tests/index.js +261 -242
- package/lib/ocaml/.compiler.log +2 -0
- package/lib/ocaml/Deser.ast +0 -0
- package/lib/ocaml/Deser.cmi +0 -0
- package/lib/ocaml/Deser.cmj +0 -0
- package/lib/ocaml/Deser.cmt +0 -0
- package/lib/ocaml/Deser.res +341 -0
- package/lib/ocaml/QUnit.ast +0 -0
- package/lib/ocaml/QUnit.cmi +0 -0
- package/lib/ocaml/QUnit.cmj +0 -0
- package/lib/ocaml/QUnit.cmt +0 -0
- package/lib/ocaml/QUnit.res +72 -0
- package/lib/ocaml/index.ast +0 -0
- package/lib/ocaml/index.cmi +0 -0
- package/lib/ocaml/index.cmj +0 -0
- package/lib/ocaml/index.cmt +0 -0
- package/lib/ocaml/index.res +212 -0
- package/lib/rescript.lock +1 -0
- package/package.json +4 -6
- package/rescript.json +4 -6
- package/src/Deser.res +19 -29
- package/tests/QUnit.res +4 -4
- package/tests/index.res +24 -1
- package/yarn.lock +683 -0
- package/lib/bs/.bsbuild +0 -0
- package/lib/bs/.bsdeps +0 -9
- package/lib/bs/.ninja_log +0 -57
- package/lib/bs/.project-files-cache +0 -0
- package/lib/bs/.sourcedirs.json +0 -1
- 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/index.cmi +0 -0
- package/lib/bs/___incremental/index.cmj +0 -0
- package/lib/bs/___incremental/index.cmt +0 -0
- package/lib/bs/install.ninja +0 -10
- package/lib/bs/src/Deser.d +0 -0
- package/lib/bs/tests/QUnit.d +0 -0
- package/lib/bs/tests/index.d +0 -1
package/rescript.json
CHANGED
|
@@ -10,16 +10,14 @@
|
|
|
10
10
|
"type": "dev"
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
|
-
"bs-dependencies": [
|
|
14
|
-
"@rescript/core"
|
|
15
|
-
],
|
|
16
|
-
"uncurried": true,
|
|
17
13
|
"package-specs": [
|
|
18
14
|
{
|
|
19
|
-
"module": "esmodule"
|
|
15
|
+
"module": "esmodule",
|
|
16
|
+
"in-source": false
|
|
20
17
|
},
|
|
21
18
|
{
|
|
22
|
-
"module": "commonjs"
|
|
19
|
+
"module": "commonjs",
|
|
20
|
+
"in-source": false
|
|
23
21
|
}
|
|
24
22
|
],
|
|
25
23
|
"warnings": {
|
package/src/Deser.res
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
open RescriptCore
|
|
2
|
-
|
|
3
1
|
module FieldValue = {
|
|
4
2
|
type t
|
|
5
3
|
external string: string => t = "%identity"
|
|
@@ -7,8 +5,8 @@ module FieldValue = {
|
|
|
7
5
|
external float: float => t = "%identity"
|
|
8
6
|
external boolean: bool => t = "%identity"
|
|
9
7
|
external array: array<t> => t = "%identity"
|
|
10
|
-
external object:
|
|
11
|
-
external mapping:
|
|
8
|
+
external object: dict<t> => t = "%identity"
|
|
9
|
+
external mapping: dict<t> => t = "%identity"
|
|
12
10
|
external any: 'a => t = "%identity"
|
|
13
11
|
@val external null: t = "undefined"
|
|
14
12
|
|
|
@@ -46,7 +44,6 @@ module Field = {
|
|
|
46
44
|
// We also allow floats and then use Date.fromTime
|
|
47
45
|
| Date
|
|
48
46
|
| Datetime // alias of Date
|
|
49
|
-
|
|
50
47
|
| Tuple(array<t>)
|
|
51
48
|
| Object(array<(string, t)>)
|
|
52
49
|
| Optional(t)
|
|
@@ -55,21 +52,17 @@ module Field = {
|
|
|
55
52
|
// difference with Object, is that you don't know the names of the
|
|
56
53
|
// expected entries.
|
|
57
54
|
| Mapping(t)
|
|
58
|
-
|
|
59
55
|
| Deserializer(module(Deserializer))
|
|
60
|
-
|
|
61
56
|
// A specialized Array of deserialized items that ignores unparsable
|
|
62
57
|
// items and returns the valid collection. This saves the user from
|
|
63
58
|
// writing 'Array(DefaultWhenInvalid(Optional(Deserializer(module(M)))))'
|
|
64
59
|
// and then post-process the list of items with 'Array.keepSome'
|
|
65
60
|
| Collection(module(Deserializer))
|
|
66
61
|
| DefaultWhenInvalid(t, FieldValue.t)
|
|
67
|
-
|
|
68
62
|
// FIXME: this is used to add additional restrictions like variadictInt or
|
|
69
63
|
// variadicString; but I find it too type-unsafe. I might consider having
|
|
70
64
|
// a Constraints for this in the future.
|
|
71
65
|
| Morphism(t, FieldValue.t => FieldValue.t)
|
|
72
|
-
|
|
73
66
|
| Self
|
|
74
67
|
|
|
75
68
|
let usingString = (f: string => 'a) => value => value->FieldValue.asString->f->FieldValue.any
|
|
@@ -85,7 +78,7 @@ module Field = {
|
|
|
85
78
|
switch i->fromJs {
|
|
86
79
|
| Some(internalValue) => internalValue
|
|
87
80
|
| None =>
|
|
88
|
-
|
|
81
|
+
throw(TypeError(`This Int(${i->Int.toString}) not a valid value here. Hint: ${hint}`))
|
|
89
82
|
}
|
|
90
83
|
}),
|
|
91
84
|
)
|
|
@@ -94,7 +87,7 @@ module Field = {
|
|
|
94
87
|
usingString(i => {
|
|
95
88
|
switch i->fromJs {
|
|
96
89
|
| Some(internalValue) => internalValue
|
|
97
|
-
| None =>
|
|
90
|
+
| None => throw(TypeError(`This String("${i}") not a valid value here. Hint: ${hint}`))
|
|
98
91
|
}
|
|
99
92
|
}),
|
|
100
93
|
)
|
|
@@ -147,12 +140,7 @@ module Field = {
|
|
|
147
140
|
}
|
|
148
141
|
}
|
|
149
142
|
|
|
150
|
-
let rec extractValue = (
|
|
151
|
-
values: Dict.t<JSON.t>,
|
|
152
|
-
field: string,
|
|
153
|
-
shape: t,
|
|
154
|
-
self: t,
|
|
155
|
-
): FieldValue.t => {
|
|
143
|
+
let rec extractValue = (values: dict<JSON.t>, field: string, shape: t, self: t): FieldValue.t => {
|
|
156
144
|
switch values->Dict.get(field) {
|
|
157
145
|
| Some(value) => value->fromUntagged(shape, self)
|
|
158
146
|
| None =>
|
|
@@ -160,7 +148,7 @@ module Field = {
|
|
|
160
148
|
| DefaultWhenInvalid(_, _) => JSON.Null->fromUntagged(shape, self)
|
|
161
149
|
| Optional(_) => JSON.Null->fromUntagged(shape, self)
|
|
162
150
|
| OptionalWithDefault(_, default) => default
|
|
163
|
-
| _ =>
|
|
151
|
+
| _ => throw(TypeError(`Missing non-optional field '${field}'`))
|
|
164
152
|
}
|
|
165
153
|
}
|
|
166
154
|
}
|
|
@@ -170,7 +158,7 @@ module Field = {
|
|
|
170
158
|
|
|
171
159
|
| (Literal(expected), String(text)) if text == expected => FieldValue.string(text)
|
|
172
160
|
| (Literal(expected), String(text)) =>
|
|
173
|
-
|
|
161
|
+
throw(TypeError(`Expecting literal ${expected}, got ${text}`))
|
|
174
162
|
|
|
175
163
|
| (String, String(text)) => FieldValue.string(text)
|
|
176
164
|
|
|
@@ -187,7 +175,7 @@ module Field = {
|
|
|
187
175
|
let values = Belt.Array.zipBy(items, bases, (i, b) => fromUntagged(i, b, self))
|
|
188
176
|
values->FieldValue.array
|
|
189
177
|
} else {
|
|
190
|
-
|
|
178
|
+
throw(
|
|
191
179
|
TypeError(`Expecting ${lenbases->Int.toString} items, got ${lenitems->Int.toString}`),
|
|
192
180
|
)
|
|
193
181
|
}
|
|
@@ -196,14 +184,14 @@ module Field = {
|
|
|
196
184
|
| (Datetime | Date, String(s)) =>
|
|
197
185
|
let r = Date.fromString(s)
|
|
198
186
|
if r->Date.getDate->Int.toFloat->Float.isNaN {
|
|
199
|
-
|
|
187
|
+
throw(TypeError(`Invalid date ${s}`))
|
|
200
188
|
}
|
|
201
189
|
r->FieldValue.any
|
|
202
190
|
|
|
203
191
|
| (Datetime | Date, Number(f)) =>
|
|
204
192
|
let r = Date.fromTime(f)
|
|
205
193
|
if r->Date.getDate->Int.toFloat->Float.isNaN {
|
|
206
|
-
|
|
194
|
+
throw(TypeError(`Invalid date ${f->Float.toString}`))
|
|
207
195
|
}
|
|
208
196
|
r->FieldValue.any
|
|
209
197
|
|
|
@@ -218,7 +206,7 @@ module Field = {
|
|
|
218
206
|
->Array.map(((field, shape)) => {
|
|
219
207
|
let value = switch extractValue(values, field, shape, self) {
|
|
220
208
|
| value => value
|
|
221
|
-
| exception TypeError(msg) =>
|
|
209
|
+
| exception TypeError(msg) => throw(TypeError(`Field "${field}": ${msg}`))
|
|
222
210
|
}
|
|
223
211
|
(field, value)
|
|
224
212
|
})
|
|
@@ -251,7 +239,7 @@ module Field = {
|
|
|
251
239
|
module M = unpack(m: Deserializer)
|
|
252
240
|
switch untagged->M.fromJSON {
|
|
253
241
|
| Ok(res) => res->FieldValue.any
|
|
254
|
-
| Error(msg) =>
|
|
242
|
+
| Error(msg) => throw(TypeError(msg))
|
|
255
243
|
}
|
|
256
244
|
}
|
|
257
245
|
|
|
@@ -265,7 +253,7 @@ module Field = {
|
|
|
265
253
|
}
|
|
266
254
|
| (Self, _) => untagged->fromUntagged(self, self)
|
|
267
255
|
| (expected, actual) =>
|
|
268
|
-
|
|
256
|
+
throw(TypeError(`Expected ${expected->toString}, but got ${actual->_taggedToString} instead`))
|
|
269
257
|
}
|
|
270
258
|
}
|
|
271
259
|
|
|
@@ -295,8 +283,9 @@ module Field = {
|
|
|
295
283
|
|
|
296
284
|
| (Object(fields), optional) =>
|
|
297
285
|
fields
|
|
298
|
-
->Array.map(((fieldName, field)) =>
|
|
299
|
-
checkFieldsSanity(`${name}::${fieldName}`, field, optional)
|
|
286
|
+
->Array.map(((fieldName, field)) =>
|
|
287
|
+
() => checkFieldsSanity(`${name}::${fieldName}`, field, optional)
|
|
288
|
+
)
|
|
300
289
|
->Array.reduce(Ok([]), (res, nextitem) =>
|
|
301
290
|
res->Result.flatMap(arr => nextitem()->Result.map(i => arr->Array.concat([i])))
|
|
302
291
|
)
|
|
@@ -308,8 +297,9 @@ module Field = {
|
|
|
308
297
|
|
|
309
298
|
| (Tuple(fields), optional) =>
|
|
310
299
|
fields
|
|
311
|
-
->Array.mapWithIndex((field, index) =>
|
|
312
|
-
checkFieldsSanity(`${name}[${index->Int.toString}]`, field, optional)
|
|
300
|
+
->Array.mapWithIndex((field, index) =>
|
|
301
|
+
() => checkFieldsSanity(`${name}[${index->Int.toString}]`, field, optional)
|
|
302
|
+
)
|
|
313
303
|
->Array.reduce(Ok([]), (res, nextitem) =>
|
|
314
304
|
res->Result.flatMap(arr => nextitem()->Result.map(i => arr->Array.concat([i])))
|
|
315
305
|
)
|
package/tests/QUnit.res
CHANGED
|
@@ -48,12 +48,12 @@ type done = unit => unit
|
|
|
48
48
|
|
|
49
49
|
@send external async: assertion => done = "async"
|
|
50
50
|
@send external asyncMany: (assertion, int) => done = "async"
|
|
51
|
-
@send external rejects: (assertion,
|
|
52
|
-
@send external rejectsM: (assertion,
|
|
51
|
+
@send external rejects: (assertion, promise<'a>, message) => unit = "rejects"
|
|
52
|
+
@send external rejectsM: (assertion, promise<'a>, message) => unit = "rejects"
|
|
53
53
|
@send
|
|
54
|
-
external rejectMatches: (assertion,
|
|
54
|
+
external rejectMatches: (assertion, promise<'a>, matcher<'a>, message) => unit = "rejects"
|
|
55
55
|
@send
|
|
56
|
-
external rejectMatchesM: (assertion,
|
|
56
|
+
external rejectMatchesM: (assertion, promise<'a>, matcher<'a>, message) => unit = "rejects"
|
|
57
57
|
@send external timeout: (assertion, int) => unit = "timeout"
|
|
58
58
|
|
|
59
59
|
// Exceptions
|
package/tests/index.res
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
open QUnit
|
|
2
|
-
open RescriptCore
|
|
3
2
|
|
|
4
3
|
module Appointment = {
|
|
5
4
|
type t = {
|
|
@@ -187,3 +186,27 @@ module_("Recursive deserializer", _ => {
|
|
|
187
186
|
qunit->deepEqual(data->Ledger.fromJSON->Obj.magic, Ok(expected), "nice ledger")
|
|
188
187
|
})
|
|
189
188
|
})
|
|
189
|
+
|
|
190
|
+
module_("Type safety limits", _ => {
|
|
191
|
+
test("ill-defined deserializer can cheat the type system", qunit => {
|
|
192
|
+
module X = Deser.MakeDeserializer({
|
|
193
|
+
type t = string
|
|
194
|
+
let fields = Deser.Field.Array(Int)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
let data = %raw("[1]")
|
|
198
|
+
qunit->throws(
|
|
199
|
+
_ => {
|
|
200
|
+
let illString = data->X.fromJSON->Result.getOr("")
|
|
201
|
+
try {
|
|
202
|
+
Console.info3(__MODULE__, "This will fail with a type error", illString->String.charAt(0))
|
|
203
|
+
} catch {
|
|
204
|
+
| e =>
|
|
205
|
+
Console.error(e)
|
|
206
|
+
throw(e)
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"Expected: TypeError: illString.charAt is not a function",
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
})
|