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

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.
Files changed (61) hide show
  1. package/README.md +13 -2
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/build.ninja +0 -27
  4. package/lib/bs/compiler-info.json +8 -0
  5. package/lib/bs/src/Deser.ast +0 -0
  6. package/lib/bs/src/Deser.cmi +0 -0
  7. package/lib/bs/src/Deser.cmj +0 -0
  8. package/lib/bs/src/Deser.cmt +0 -0
  9. package/lib/bs/{___incremental → src}/Deser.res +19 -29
  10. package/lib/bs/tests/QUnit.ast +0 -0
  11. package/lib/bs/tests/QUnit.cmi +0 -0
  12. package/lib/bs/tests/QUnit.cmj +0 -0
  13. package/lib/bs/tests/QUnit.cmt +0 -0
  14. package/lib/bs/tests/QUnit.res +72 -0
  15. package/lib/bs/tests/index.ast +0 -0
  16. package/lib/bs/tests/index.cmi +0 -0
  17. package/lib/bs/tests/index.cmj +0 -0
  18. package/lib/bs/tests/index.cmt +0 -0
  19. package/lib/bs/{___incremental → tests}/index.res +24 -1
  20. package/lib/es6/src/Deser.js +297 -365
  21. package/lib/es6/tests/index.js +260 -241
  22. package/lib/js/src/Deser.js +293 -361
  23. package/lib/js/tests/index.js +261 -242
  24. package/lib/ocaml/.compiler.log +2 -0
  25. package/lib/ocaml/Deser.ast +0 -0
  26. package/lib/ocaml/Deser.cmi +0 -0
  27. package/lib/ocaml/Deser.cmj +0 -0
  28. package/lib/ocaml/Deser.cmt +0 -0
  29. package/lib/ocaml/Deser.res +341 -0
  30. package/lib/ocaml/QUnit.ast +0 -0
  31. package/lib/ocaml/QUnit.cmi +0 -0
  32. package/lib/ocaml/QUnit.cmj +0 -0
  33. package/lib/ocaml/QUnit.cmt +0 -0
  34. package/lib/ocaml/QUnit.res +72 -0
  35. package/lib/ocaml/index.ast +0 -0
  36. package/lib/ocaml/index.cmi +0 -0
  37. package/lib/ocaml/index.cmj +0 -0
  38. package/lib/ocaml/index.cmt +0 -0
  39. package/lib/ocaml/index.res +212 -0
  40. package/lib/rescript.lock +1 -0
  41. package/package.json +4 -6
  42. package/rescript.json +4 -6
  43. package/src/Deser.res +19 -29
  44. package/tests/QUnit.res +4 -4
  45. package/tests/index.res +24 -1
  46. package/yarn.lock +683 -0
  47. package/lib/bs/.bsbuild +0 -0
  48. package/lib/bs/.bsdeps +0 -9
  49. package/lib/bs/.ninja_log +0 -57
  50. package/lib/bs/.project-files-cache +0 -0
  51. package/lib/bs/.sourcedirs.json +0 -1
  52. package/lib/bs/___incremental/Deser.cmi +0 -0
  53. package/lib/bs/___incremental/Deser.cmj +0 -0
  54. package/lib/bs/___incremental/Deser.cmt +0 -0
  55. package/lib/bs/___incremental/index.cmi +0 -0
  56. package/lib/bs/___incremental/index.cmj +0 -0
  57. package/lib/bs/___incremental/index.cmt +0 -0
  58. package/lib/bs/install.ninja +0 -10
  59. package/lib/bs/src/Deser.d +0 -0
  60. package/lib/bs/tests/QUnit.d +0 -0
  61. package/lib/bs/tests/index.d +0 -1
package/README.md CHANGED
@@ -104,8 +104,7 @@ module type Serializable = {
104
104
  `Optional`, `OptionalWithDefault`.
105
105
 
106
106
  - `Mapping(Field.t)`, parses a JSON object with unknown keys (of type string)
107
- and a given type of value. Valid values have the internal type
108
- `Prelude.Dict.t`.
107
+ and a given type of value. Valid values have the internal type `dict<_>`.
109
108
 
110
109
  - `Deserializer(module(Deserializer))`, parses an JSON object with the function
111
110
  `fromJSON` of another deserializer. This allows the composition of
@@ -136,6 +135,18 @@ type rec Node<'t> = Leave('t) | (Branch(array<Node<'t>>)
136
135
  Cannot be automatically deserialized.
137
136
 
138
137
 
138
+ # Type safety disclaimer
139
+
140
+ `Deser` cannot guarantee type safety for ill-defined deserializers, the following deserializer will accept JSON payloads of the wrong type that can cause runtime type errors:
141
+
142
+ ```rescript
143
+ module X = Deser.MakeDeserializer({
144
+ type t = string
145
+ let fields = Deser.Field.Array(Int)
146
+ })
147
+ ```
148
+
149
+
139
150
  ## License
140
151
 
141
152
  The MIT License
@@ -1,2 +1,2 @@
1
- #Start(1763479665497)
2
- #Done(1763479665497)
1
+ #Start(1766181917581)
2
+ #Done(1766181917581)
@@ -1,27 +0,0 @@
1
- rescript = 1
2
- g_finger := /home/manu/src/kaiko/rescript-deser/node_modules/@rescript/core/lib/ocaml/install.stamp
3
- rule astj
4
- command = /home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/bsc.exe -warn-error +8+11+26+33+56 -bs-v 11.1.4 -uncurried -absname -bs-ast -o $out $i
5
- o tests/index.ast : astj ../../tests/index.res
6
- rule deps_dev
7
- command = /home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/bsb_helper.exe -g -hash 9a7a4be86983dcab705bb67a1ec483c7 $in
8
- restat = 1
9
- o tests/index.d : deps_dev tests/index.ast
10
- rule mij_dev
11
- command = /home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/bsc.exe -I tests -I src/ -I /home/manu/src/kaiko/rescript-deser/node_modules/@rescript/core/lib/ocaml -warn-error +8+11+26+33+56 -uncurried -bs-package-name @kaiko.io/rescript-deser -bs-package-output commonjs:lib/js/$in_d:.js -bs-package-output esmodule:lib/es6/$in_d:.js -bs-v $g_finger $i
12
- dyndep = 1
13
- restat = 1
14
- o tests/index.cmj tests/index.cmi ../es6/tests/index.js ../js/tests/index.js : mij_dev tests/index.ast
15
- o tests/QUnit.ast : astj ../../tests/QUnit.res
16
- o tests/QUnit.d : deps_dev tests/QUnit.ast
17
- o tests/QUnit.cmj tests/QUnit.cmi ../es6/tests/QUnit.js ../js/tests/QUnit.js : mij_dev tests/QUnit.ast
18
- o src/Deser.ast : astj ../../src/Deser.res
19
- rule deps
20
- command = /home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/bsb_helper.exe -hash 9a7a4be86983dcab705bb67a1ec483c7 $in
21
- restat = 1
22
- o src/Deser.d : deps src/Deser.ast
23
- rule mij
24
- command = /home/manu/src/kaiko/rescript-deser/node_modules/rescript/linux/bsc.exe -I src/ -I /home/manu/src/kaiko/rescript-deser/node_modules/@rescript/core/lib/ocaml -warn-error +8+11+26+33+56 -uncurried -bs-package-name @kaiko.io/rescript-deser -bs-package-output commonjs:lib/js/$in_d:.js -bs-package-output esmodule:lib/es6/$in_d:.js -bs-v $g_finger $i
25
- dyndep = 1
26
- restat = 1
27
- o src/Deser.cmj src/Deser.cmi ../es6/src/Deser.js ../js/src/Deser.js : mij src/Deser.ast
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": "12.0.1",
3
+ "bsc_path": "/home/manu/src/kaiko/rescript-deser/node_modules/@rescript/linux-x64/bin/bsc.exe",
4
+ "bsc_hash": "a2b93197b8c05fc70981fe131a9ed75f8462a9f615f71055f306c9deb058d3cf",
5
+ "rescript_config_hash": "6e13fe97e162573c9ab73327567294583b34d549775f8391e1d5c8ccc0c5d580",
6
+ "runtime_path": "/home/manu/src/kaiko/rescript-deser/node_modules/@rescript/runtime",
7
+ "generated_at": "1766181917582"
8
+ }
Binary file
Binary file
Binary file
Binary file
@@ -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: Dict.t<t> => t = "%identity"
11
- external mapping: Dict.t<t> => t = "%identity"
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
- raise(TypeError(`This Int(${i->Int.toString}) not a valid value here. Hint: ${hint}`))
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 => raise(TypeError(`This String("${i}") not a valid value here. Hint: ${hint}`))
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
- | _ => raise(TypeError(`Missing non-optional field '${field}'`))
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
- raise(TypeError(`Expecting literal ${expected}, got ${text}`))
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
- raise(
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
- raise(TypeError(`Invalid date ${s}`))
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
- raise(TypeError(`Invalid date ${f->Float.toString}`))
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) => raise(TypeError(`Field "${field}": ${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) => raise(TypeError(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
- raise(TypeError(`Expected ${expected->toString}, but got ${actual->_taggedToString} instead`))
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
  )
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,72 @@
1
+ type description = string
2
+ type message = string
3
+
4
+ type result<'a> = {
5
+ result: bool,
6
+ actual: 'a,
7
+ expected: 'a,
8
+ message: string,
9
+ }
10
+
11
+ type assertion
12
+ type block = assertion => unit
13
+ type matcher<'a> = 'a => bool
14
+
15
+ // Library stuff
16
+
17
+ @scope("QUnit") external start: unit => unit = "start"
18
+ @scope("QUnit") external done: (unit => unit) => unit = "done"
19
+
20
+ @send external expect: (assertion, int) => unit = "expect"
21
+ @send external pushResult: (assertion, result<'a>) => unit = "notOk"
22
+ @send external step: (assertion, description) => unit = "step"
23
+ @send external verifySteps: (assertion, array<description>) => unit = "verifySteps"
24
+
25
+ // Values
26
+
27
+ @send external equal: (assertion, 'a, 'a, description) => unit = "equal"
28
+ @send external notEqual: (assertion, 'a, 'a, description) => unit = "notEqual"
29
+
30
+ @send external isFalse: (assertion, 'a, description) => unit = "false"
31
+ @send external isTrue: (assertion, 'a, description) => unit = "true"
32
+
33
+ @send external deepEqual: (assertion, 'a, 'a, description) => unit = "deepEqual"
34
+ @send external notDeepEqual: (assertion, 'a, 'a, description) => unit = "notDeepEqual"
35
+
36
+ @send external strictEqual: (assertion, 'a, 'a, description) => unit = "strictEqual"
37
+ @send external notStrictEqual: (assertion, 'a, 'a, description) => unit = "notStrictEqual"
38
+
39
+ @send external ok: (assertion, 'a, 'a) => unit = "ok"
40
+ @send external notOk: (assertion, 'a, 'a) => unit = "notOk"
41
+
42
+ @send external propEqual: (assertion, 'a, 'a) => unit = "propEqual"
43
+ @send external notPropEqual: (assertion, 'a, 'a) => unit = "notPropEqual"
44
+
45
+ // Promises
46
+
47
+ type done = unit => unit
48
+
49
+ @send external async: assertion => done = "async"
50
+ @send external asyncMany: (assertion, int) => done = "async"
51
+ @send external rejects: (assertion, promise<'a>, message) => unit = "rejects"
52
+ @send external rejectsM: (assertion, promise<'a>, message) => unit = "rejects"
53
+ @send
54
+ external rejectMatches: (assertion, promise<'a>, matcher<'a>, message) => unit = "rejects"
55
+ @send
56
+ external rejectMatchesM: (assertion, promise<'a>, matcher<'a>, message) => unit = "rejects"
57
+ @send external timeout: (assertion, int) => unit = "timeout"
58
+
59
+ // Exceptions
60
+
61
+ @send external throws: (assertion, block, message) => unit = "throws"
62
+ @send
63
+ external throwMatches: (assertion, block, matcher<'a>, message) => unit = "throws"
64
+
65
+ type hooks
66
+ @send external before: (hooks, block) => unit = "before"
67
+ @send external beforeEach: (hooks, block) => unit = "beforeEach"
68
+ @send external afterEach: (hooks, block) => unit = "afterEach"
69
+ @send external after: (hooks, block) => unit = "after"
70
+
71
+ @module("qunit") @val external module_: (description, hooks => unit) => unit = "module"
72
+ @module("qunit") @val external test: (description, block) => unit = "test"
Binary file
Binary file
Binary file
Binary file
@@ -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
+ })