@kaiko.io/rescript-deser 4.0.0-rc.3 → 4.0.0-rc.5

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,283 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+ 'use strict';
3
+
4
+ var $$JSON = require("../src/JSON.js");
5
+ var Curry = require("rescript/lib/js/curry.js");
6
+ var Qunit = require("qunit");
7
+ var Prelude = require("@kaiko.io/rescript-prelude/lib/js/src/Prelude.js");
8
+
9
+ var fields = {
10
+ TAG: "Object",
11
+ _0: [
12
+ [
13
+ "note",
14
+ "String"
15
+ ],
16
+ [
17
+ "date",
18
+ "Date"
19
+ ],
20
+ [
21
+ "extra",
22
+ {
23
+ TAG: "Optional",
24
+ _0: "String"
25
+ }
26
+ ]
27
+ ]
28
+ };
29
+
30
+ var Deserializer = $$JSON.MakeDeserializer({
31
+ fields: fields
32
+ });
33
+
34
+ var Appointment = {
35
+ Deserializer: Deserializer
36
+ };
37
+
38
+ Qunit.module("Basic deserializer", (function (param) {
39
+ var valid = [
40
+ [
41
+ {"note": "Bicentennial doctor's appointment", "date": "2100-01-01"},
42
+ {
43
+ note: "Bicentennial doctor's appointment",
44
+ date: new Date("2100-01-01"),
45
+ extra: undefined
46
+ }
47
+ ],
48
+ [
49
+ {
50
+ "note": "Bicentennial doctor's appointment",
51
+ "date": "2100-01-01",
52
+ "extra": "Don't take your stop-aging pills the night before the appointment",
53
+ },
54
+ {
55
+ note: "Bicentennial doctor's appointment",
56
+ date: new Date("2100-01-01"),
57
+ extra: "Don't take your stop-aging pills the night before the appointment"
58
+ }
59
+ ]
60
+ ];
61
+ Qunit.test("Correctly deserializes data", (function (qunit) {
62
+ qunit.expect(valid.length);
63
+ Curry._2(Prelude.$$Array.forEach, valid, (function (param) {
64
+ var data = param[0];
65
+ console.log("Running sample", data);
66
+ var result = Deserializer.fromJSON(data);
67
+ if (result.TAG === "Ok") {
68
+ qunit.deepEqual(result._0, param[1], "result == expected");
69
+ return ;
70
+ }
71
+ console.error(result._0);
72
+ }));
73
+ }));
74
+ var invalid = [[
75
+ "Missing non-optional field",
76
+ {"extra": "Bicentennial doctor's appointment", "date": "2100-01-01"}
77
+ ]];
78
+ Qunit.test("Correctly catches invalid data", (function (qunit) {
79
+ qunit.expect(invalid.length);
80
+ Curry._2(Prelude.$$Array.forEach, invalid, (function (param) {
81
+ var data = param[1];
82
+ console.log("Running sample", param[0], data);
83
+ var result = Deserializer.fromJSON(data);
84
+ if (result.TAG === "Ok") {
85
+ console.error("Invalid being accepted: ", result._0);
86
+ return ;
87
+ }
88
+ console.log("Correctly detected:", result._0);
89
+ qunit.ok(true, true);
90
+ }));
91
+ }));
92
+ }));
93
+
94
+ Qunit.module("Recursive deserializer", (function (param) {
95
+ var fields = {
96
+ TAG: "Object",
97
+ _0: [
98
+ [
99
+ "data",
100
+ "Any"
101
+ ],
102
+ [
103
+ "children",
104
+ {
105
+ TAG: "Array",
106
+ _0: "Self"
107
+ }
108
+ ]
109
+ ]
110
+ };
111
+ var DeserializerImpl = $$JSON.MakeDeserializer({
112
+ fields: fields
113
+ });
114
+ var checkFieldsSanity = DeserializerImpl.checkFieldsSanity;
115
+ var fromJSON = function (data) {
116
+ return Curry._2(Prelude.Result.map, DeserializerImpl.fromJSON(data), (function (prim) {
117
+ return prim;
118
+ }));
119
+ };
120
+ var valid = [[
121
+ {"data": "A", "children": [{"data": "A1", "children": []}, {"data": "B", "children": [{"data": "C", "children": []}, {"data": "D", "children": []}]}]},
122
+ {
123
+ data: "A",
124
+ children: [
125
+ {
126
+ data: "A1",
127
+ children: []
128
+ },
129
+ {
130
+ data: "B",
131
+ children: [
132
+ {
133
+ data: "C",
134
+ children: []
135
+ },
136
+ {
137
+ data: "D",
138
+ children: []
139
+ }
140
+ ]
141
+ }
142
+ ]
143
+ }
144
+ ]];
145
+ Qunit.test("Trivial recursion detection: Ok", (function (qunit) {
146
+ qunit.expect(1);
147
+ qunit.deepEqual(checkFieldsSanity(), {
148
+ TAG: "Ok",
149
+ _0: undefined
150
+ }, "Ok");
151
+ }));
152
+ Qunit.test("Infinite list", (function (qunit) {
153
+ var fields = {
154
+ TAG: "Object",
155
+ _0: [
156
+ [
157
+ "head",
158
+ "String"
159
+ ],
160
+ [
161
+ "tail",
162
+ "Self"
163
+ ]
164
+ ]
165
+ };
166
+ var InfiniteList = $$JSON.MakeDeserializer({
167
+ fields: fields
168
+ });
169
+ qunit.expect(1);
170
+ qunit.deepEqual(Curry._1(Prelude.Result.isError, InfiniteList.checkFieldsSanity()), true, "Ok");
171
+ }));
172
+ Qunit.test("Finite list", (function (qunit) {
173
+ var fields = {
174
+ TAG: "Object",
175
+ _0: [
176
+ [
177
+ "head",
178
+ "String"
179
+ ],
180
+ [
181
+ "tail",
182
+ {
183
+ TAG: "Optional",
184
+ _0: "Self"
185
+ }
186
+ ]
187
+ ]
188
+ };
189
+ var List = $$JSON.MakeDeserializer({
190
+ fields: fields
191
+ });
192
+ qunit.expect(1);
193
+ qunit.deepEqual(List.checkFieldsSanity(), {
194
+ TAG: "Ok",
195
+ _0: undefined
196
+ }, "Ok");
197
+ }));
198
+ Qunit.test("Correctly deserializes recursive data", (function (qunit) {
199
+ qunit.expect(valid.length);
200
+ Curry._2(Prelude.$$Array.forEach, valid, (function (param) {
201
+ var data = param[0];
202
+ console.log("Running sample", data);
203
+ var result = fromJSON(data);
204
+ if (result.TAG === "Ok") {
205
+ qunit.deepEqual(result._0, param[1], "result == expected");
206
+ return ;
207
+ }
208
+ console.error(result._0);
209
+ }));
210
+ }));
211
+ Qunit.test("Recursion in sub-deserializer", (function (qunit) {
212
+ var fields = {
213
+ TAG: "Object",
214
+ _0: [
215
+ [
216
+ "head",
217
+ "String"
218
+ ],
219
+ [
220
+ "tail",
221
+ {
222
+ TAG: "Optional",
223
+ _0: "Self"
224
+ }
225
+ ]
226
+ ]
227
+ };
228
+ var List = $$JSON.MakeDeserializer({
229
+ fields: fields
230
+ });
231
+ var fields$1 = {
232
+ TAG: "Object",
233
+ _0: [
234
+ [
235
+ "records",
236
+ {
237
+ TAG: "Deserializer",
238
+ _0: List
239
+ }
240
+ ],
241
+ [
242
+ "next",
243
+ {
244
+ TAG: "Optional",
245
+ _0: "Self"
246
+ }
247
+ ]
248
+ ]
249
+ };
250
+ var Ledger = $$JSON.MakeDeserializer({
251
+ fields: fields$1
252
+ });
253
+ var data = {"records": {"head": "A", "tail": {"head": "B"}},
254
+ "next": {"records": {"head": "A", "tail": {"head": "B"}}}};
255
+ var expected = {
256
+ records: {
257
+ head: "A",
258
+ tail: {
259
+ head: "B",
260
+ tail: undefined
261
+ }
262
+ },
263
+ next: {
264
+ records: {
265
+ head: "A",
266
+ tail: {
267
+ head: "B",
268
+ tail: undefined
269
+ }
270
+ },
271
+ next: undefined
272
+ }
273
+ };
274
+ qunit.expect(1);
275
+ qunit.deepEqual(Ledger.fromJSON(data), {
276
+ TAG: "Ok",
277
+ _0: expected
278
+ }, "nice ledger");
279
+ }));
280
+ }));
281
+
282
+ exports.Appointment = Appointment;
283
+ /* Deserializer Not a pure module */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaiko.io/rescript-deser",
3
- "version": "4.0.0-rc.3",
3
+ "version": "4.0.0-rc.5",
4
4
  "keywords": [
5
5
  "json",
6
6
  "deserializer",
@@ -20,11 +20,11 @@
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.6"
24
24
  },
25
25
  "devDependencies": {
26
26
  "esbuild": "^0.15.7",
27
27
  "qunit": "^2.16.0",
28
- "rescript": "11.0.0-rc.6"
28
+ "rescript": "11.0.0-rc.7"
29
29
  }
30
30
  }
@@ -1,2 +0,0 @@
1
- #Start(1700763106532)
2
- #Done(1700763106537)
@@ -1,6 +0,0 @@
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
Binary file
Binary file
Binary file
@@ -1,343 +0,0 @@
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