diff --git a/src/S.resi b/src/S.resi index 3deb793..239080f 100644 --- a/src/S.resi +++ b/src/S.resi @@ -55,7 +55,7 @@ and tagged = | Dict(t) | JSON({validated: bool}) and item = { - @as("t") + @as("s") schema: schema, @as("p") path: Path.t, @@ -63,8 +63,6 @@ and item = { location: string, @as("i") inlinedLocation: string, - @as("s") - symbol: Js.Types.symbol, } and schema<'value> = t<'value> and error = private {operation: operation, code: errorCode, path: Path.t} diff --git a/src/S_Core.bs.mjs b/src/S_Core.bs.mjs index b39e817..a4c66fe 100644 --- a/src/S_Core.bs.mjs +++ b/src/S_Core.bs.mjs @@ -7,6 +7,8 @@ import * as Caml_option from "rescript/lib/es6/caml_option.js"; import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js"; import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js"; +var immutableEmpty = {}; + function fromString(string) { return JSON.stringify(string); } @@ -1117,8 +1119,6 @@ function recursive(fn) { }; var schema = fn(placeholder); placeholder.f = schema.f; - schema.d = undefined; - schema.c = undefined; var initialParseOperationBuilder = schema.b; schema.b = (function (b, input, selfSchema, path) { var bb = scope(b); @@ -1499,13 +1499,13 @@ function addItemOutput(b, ctx, item, output) { function toOutputVal(b, ctx, outputDefinition) { var definitionToValue = function (definition, outputPath) { - var val = ctx.o.get(definition); - if (val !== undefined) { - return inline(b, val); - } if (!(typeof definition === "object" && definition !== null)) { return "e[" + (b.g.e.push(definition) - 1) + "]"; } + var val = ctx.o.get(definition[itemSymbol]); + if (val !== undefined) { + return inline(b, val); + } var isArray = Array.isArray(definition); var keys = Object.keys(definition); var codeRef = isArray ? "[" : "{"; @@ -1547,7 +1547,7 @@ function processInputItems(b, ctx, input, schema, path) { b.c = ""; var item = items[idx]; var itemPath = item.p; - var schema$1 = item.t; + var schema$1 = item.s; var itemInput = val(b, inputVar + itemPath); var path$1 = path + itemPath; var isLiteral = schema$1.t.TAG === "Literal"; @@ -1556,12 +1556,8 @@ function processInputItems(b, ctx, input, schema, path) { b.c = b.c + typeFilterCode(b, typeFilter, schema$1, itemInput, path$1); } var bb = scope(b); - if (isObject && schema$1.d) { - processInputItems(bb, ctx, itemInput, schema$1, path$1); - } else { - var itemOutput = schema$1.b(bb, itemInput, schema$1, path$1); - addItemOutput(b, ctx, item, itemOutput); - } + var itemOutput = schema$1.b(bb, itemInput, schema$1, path$1); + addItemOutput(b, ctx, item, itemOutput); if (isLiteral) { b.c = b.c + allocateScope(bb) + prevCode; } else { @@ -1602,10 +1598,22 @@ function builder$1(b, input, selfSchema, path) { function name() { var schema = this; return "Object({" + schema.t.items.map(function (item) { - return item.i + ": " + item.t.n(); + return item.i + ": " + item.s.n(); }).join(", ") + "})"; } +function proxify(item) { + return new Proxy(immutableEmpty, { + get: (function (param, prop) { + if (prop === itemSymbol) { + return item; + } else { + return (void 0); + } + }) + }); +} + function reverse$1(inputDefinition, toItem) { return function () { var original = this; @@ -1623,19 +1631,20 @@ function reverse$1(inputDefinition, toItem) { var embededOutputs = new WeakMap(); var definitionToOutput = function (definition, outputPath) { if (typeof definition === "object" && definition !== null) { - if (definition.s === itemSymbol) { - var embededOutput = embededOutputs.get(definition); + var item = definition[itemSymbol]; + if (item !== undefined) { + var embededOutput = embededOutputs.get(item); if (embededOutput !== undefined) { var itemInput = outputPath === "" ? input : val(b, inputVar + outputPath); - var schema = definition.t.r(); + var schema = item.s.r(); var itemOutput = schema.b(b, itemInput, schema, path + outputPath); - b.c = b.c + ("if(" + $$var(b, embededOutput) + "!==" + $$var(b, itemOutput) + "){" + fail(b, "Multiple sources provided not equal data for " + definition.i, path) + "}"); + b.c = b.c + ("if(" + $$var(b, embededOutput) + "!==" + $$var(b, itemOutput) + "){" + fail(b, "Multiple sources provided not equal data for " + item.i, path) + "}"); return ; } var itemInput$1 = outputPath === "" ? input : val(b, inputVar + outputPath); - var schema$1 = definition.t.r(); + var schema$1 = item.s.r(); var itemOutput$1 = schema$1.b(b, itemInput$1, schema$1, path + outputPath); - embededOutputs.set(definition, itemOutput$1); + embededOutputs.set(item, itemOutput$1); return ; } var keys = Object.keys(definition); @@ -1659,6 +1668,14 @@ function reverse$1(inputDefinition, toItem) { definitionToOutput(inputDefinition, ""); b.c = ctx.d + b.c; var isRootObject = original.t.TAG === "Object"; + var fallbackOutput = function (item, path) { + var itemSchema = item.s; + if (itemSchema.t.TAG !== "Literal") { + return invalidOperation(b, path, "Schema for " + item.i + " isn't registered"); + } + var value = itemSchema.t._0.value; + return "e[" + (b.g.e.push(value) - 1) + "]"; + }; var schemaOutput = function (schema, isObject, path) { var items = schema.t.items; var output = ""; @@ -1679,18 +1696,6 @@ function reverse$1(inputDefinition, toItem) { return "[" + output + "]"; } }; - var fallbackOutput = function (item, path) { - var itemSchema = item.t; - if (itemSchema.t.TAG !== "Literal") { - if (isRootObject && itemSchema.d) { - return schemaOutput(itemSchema, true, path + item.p); - } else { - return invalidOperation(b, path, "Schema for " + item.i + " isn't registered"); - } - } - var value = itemSchema.t._0.value; - return "e[" + (b.g.e.push(value) - 1) + "]"; - }; if (toItem === undefined) { return val(b, schemaOutput(original, isRootObject, path)); } @@ -1708,37 +1713,25 @@ function factory$3(definer) { var fields = {}; var items = []; var flatten = function (schema) { - if (schema.d) { - return schema.d(this); - } var message = "The " + schema.n() + " schema can't be flattened"; throw new Error("[rescript-schema] " + message); }; var field = function (fieldName, schema) { var inlinedLocation = JSON.stringify(fieldName); - var item = fields[fieldName]; - if (item !== undefined) { - if (item.t.d && schema.d) { - return schema.d(item.t.c); - } + var _item = fields[fieldName]; + if (_item !== undefined) { throw new Error("[rescript-schema] " + ("The field " + inlinedLocation + " defined twice with incompatible schemas")); } - var schema$1 = schema.d ? factory$3(schema.d) : schema; var item_p = "[" + inlinedLocation + "]"; - var item$1 = { - t: schema$1, + var item = { + s: schema, p: item_p, l: fieldName, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; - fields[fieldName] = item$1; - items.push(item$1); - if (schema$1.d) { - return schema$1.t.definition; - } else { - return item$1; - } + fields[fieldName] = item; + items.push(item); + return proxify(item); }; var tag = function (tag$1, asValue) { field(tag$1, literal(asValue)); @@ -1747,15 +1740,12 @@ function factory$3(definer) { return field(fieldName, getOr(factory(schema), or)); }; var nestedField = function (fieldName, nestedFieldName, schema) { - var item = fields[fieldName]; - if (item === undefined) { + var _item = fields[fieldName]; + if (_item === undefined) { return field(fieldName, factory$3(function (s) { return s.f(nestedFieldName, schema); })); } - if (item.t.d) { - return item.t.c.f(nestedFieldName, schema); - } var message = "The field " + JSON.stringify(fieldName) + " defined twice with incompatible schemas"; throw new Error("[rescript-schema] " + message); }; @@ -1781,8 +1771,6 @@ function factory$3(definer) { b: builder$1, f: typeFilter$1, i: 0, - d: definer, - c: ctx, m: empty, parseOrThrow: initialParseOrRaise, parse: jsParse, @@ -1795,19 +1783,49 @@ function factory$3(definer) { } function to(schema, definer) { - if (schema.d) { - return factory$3(function (ctx) { - return definer(schema.d(ctx)); - }); - } var item = { - t: schema, + s: schema, p: "", l: "", - i: "\"\"", - s: itemSymbol + i: "\"\"" }; - var definition = definer(item); + var definition = definer(proxify(item)); + var outputItems = []; + var traverseDefinition = function (definition, path) { + if (typeof definition === "object" && definition !== null) { + var item = definition[itemSymbol]; + if (item !== undefined) { + outputItems.push({ + TAG: "Target", + path: path, + item: item + }); + return ; + } + var isArray = Array.isArray(definition); + outputItems.push(isArray ? ({ + TAG: "Tuple", + path: path + }) : ({ + TAG: "Object", + path: path + })); + var keys = Object.keys(definition); + for(var idx = 0 ,idx_finish = keys.length; idx < idx_finish; ++idx){ + var key = keys[idx]; + var definition$1 = definition[key]; + traverseDefinition(definition$1, path + ("[" + JSON.stringify(key) + "]")); + } + outputItems.push("ExitNode"); + return ; + } + outputItems.push({ + TAG: "Constant", + path: path, + value: definition + }); + }; + traverseDefinition(definition, ""); return makeSchema(schema.n, schema.t, schema.m, (function (b, input, selfSchema, path) { var ctx = make$1(selfSchema); var itemOutput = schema.b(b, input, schema, path); @@ -1834,8 +1852,6 @@ function setUnknownKeys(schema, unknownKeys) { b: schema.b, f: schema.f, i: schema.i, - d: schema.d, - c: schema.c, m: schema.m, parseOrThrow: initialParseOrRaise, parse: jsParse, @@ -1859,7 +1875,7 @@ function strict(schema) { function name$1() { var schema = this; return "Tuple(" + schema.t.items.map(function (item) { - return item.t.n(); + return item.s.n(); }).join(", ") + ")"; } @@ -1873,14 +1889,13 @@ function factory$4(definer) { } var item_p = "[" + inlinedLocation + "]"; var item$1 = { - t: schema, + s: schema, p: item_p, l: $$location, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; items[idx] = item$1; - return item$1; + return proxify(item$1); }; var tag = function (idx, asValue) { item(idx, literal(asValue)); @@ -1897,11 +1912,10 @@ function factory$4(definer) { var inlinedLocation = "\"" + $$location + "\""; var item_p = "[" + inlinedLocation + "]"; var item$1 = { - t: unit, + s: unit, p: item_p, l: $$location, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; items[idx] = item$1; } @@ -2351,13 +2365,12 @@ function definitionToSchema(definition) { var $$location = idx.toString(); var inlinedLocation = "\"" + $$location + "\""; var schema = definitionToSchema(definition[idx]); - var item_p = "[" + inlinedLocation + "]"; + var path = "[" + inlinedLocation + "]"; var item = { - t: schema, - p: item_p, + s: schema, + p: path, l: $$location, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; items[idx] = item; if (!isTransformed && schema !== schema.r()) { @@ -2365,33 +2378,35 @@ function definitionToSchema(definition) { } } + var definition$1 = items.map(proxify); var length = items.length; return makeSchema(name$1, { TAG: "Tuple", items: items, - definition: items + definition: definition$1 }, empty, builder$1, (function (b, inputVar) { return typeFilter(b, inputVar) + ("||" + inputVar + ".length!==" + length); - }), isTransformed ? reverse$1(items, undefined) : toSelf); + }), isTransformed ? reverse$1(definition$1, undefined) : toSelf); } var items$1 = []; var fields = {}; + var definition$2 = {}; var fieldNames = Object.keys(definition); var isTransformed$1 = false; for(var idx$1 = 0 ,idx_finish$1 = fieldNames.length; idx$1 < idx_finish$1; ++idx$1){ var $$location$1 = fieldNames[idx$1]; var inlinedLocation$1 = "\"" + $$location$1 + "\""; var schema$1 = definitionToSchema(definition[$$location$1]); - var item_p$1 = "[" + inlinedLocation$1 + "]"; + var item_p = "[" + inlinedLocation$1 + "]"; var item$1 = { - t: schema$1, - p: item_p$1, + s: schema$1, + p: item_p, l: $$location$1, - i: inlinedLocation$1, - s: itemSymbol + i: inlinedLocation$1 }; items$1[idx$1] = item$1; fields[$$location$1] = item$1; + definition$2[$$location$1] = proxify(item$1); if (!isTransformed$1 && schema$1 !== schema$1.r()) { isTransformed$1 = true; } @@ -2402,8 +2417,8 @@ function definitionToSchema(definition) { items: items$1, fields: fields, unknownKeys: globalConfig.u, - definition: fields - }, empty, builder$1, typeFilter$1, isTransformed$1 ? reverse$1(fields, undefined) : toSelf); + definition: definition$2 + }, empty, builder$1, typeFilter$1, isTransformed$1 ? reverse$1(definition$2, undefined) : toSelf); } function matches(schema) { @@ -2534,7 +2549,7 @@ function internalInline(schema, maybeVariant, param) { case "Object" : var items = literal.items; inlinedSchema = items.length !== 0 ? "S.object(s =>\n {\n " + items.map(function (item) { - return item.i + ": s.field(" + item.i + ", " + internalInline(item.t, undefined, undefined) + ")"; + return item.i + ": s.field(" + item.i + ", " + internalInline(item.s, undefined, undefined) + ")"; }).join(",\n ") + ",\n }\n)" : "S.object(_ => ())"; break; case "Tuple" : @@ -2550,25 +2565,25 @@ function internalInline(schema, maybeVariant, param) { break; case 1 : var i1 = tupleSchemas[0]; - inlinedSchema = "S.tuple1(" + internalInline(i1.t, undefined, undefined) + ")"; + inlinedSchema = "S.tuple1(" + internalInline(i1.s, undefined, undefined) + ")"; break; case 2 : var i1$1 = tupleSchemas[0]; var i2 = tupleSchemas[1]; - inlinedSchema = "S.tuple2(" + internalInline(i1$1.t, undefined, undefined) + ", " + internalInline(i2.t, undefined, undefined) + ")"; + inlinedSchema = "S.tuple2(" + internalInline(i1$1.s, undefined, undefined) + ", " + internalInline(i2.s, undefined, undefined) + ")"; break; case 3 : var i1$2 = tupleSchemas[0]; var i2$1 = tupleSchemas[1]; var i3 = tupleSchemas[2]; - inlinedSchema = "S.tuple3(" + internalInline(i1$2.t, undefined, undefined) + ", " + internalInline(i2$1.t, undefined, undefined) + ", " + internalInline(i3.t, undefined, undefined) + ")"; + inlinedSchema = "S.tuple3(" + internalInline(i1$2.s, undefined, undefined) + ", " + internalInline(i2$1.s, undefined, undefined) + ", " + internalInline(i3.s, undefined, undefined) + ")"; break; } } if (exit === 1) { inlinedSchema = "S.tuple(s => (" + tupleSchemas.map(function (item, idx) { - return "s.item(" + idx + ", " + internalInline(item.t, undefined, undefined) + ")"; + return "s.item(" + idx + ", " + internalInline(item.s, undefined, undefined) + ")"; }).join(", ") + "))"; } break; diff --git a/src/S_Core.res b/src/S_Core.res index 8afaf21..0e45db2 100644 --- a/src/S_Core.res +++ b/src/S_Core.res @@ -11,6 +11,13 @@ module Obj = { } module Stdlib = { + module Proxy = { + type traps<'a> = {get?: (~target: 'a, ~prop: unknown) => unknown} + + @new + external make: ('a, traps<'a>) => 'a = "Proxy" + } + module Option = { external unsafeUnwrap: option<'a> => 'a = "%identity" } @@ -40,6 +47,8 @@ module Stdlib = { } module Object = { + let immutableEmpty = %raw(`{}`) + @val external internalClass: Js.Types.obj_val => string = "Object.prototype.toString.call" } @@ -91,6 +100,9 @@ module Stdlib = { @get_index external unsafeGetOption: (dict<'a>, string) => option<'a> = "" + @get_index + external unsafeGetOptionBySymbol: (dict<'a>, Js.Types.symbol) => option<'a> = "" + @inline let has = (dict, key) => { dict->Js.Dict.unsafeGet(key)->(Obj.magic: 'a => bool) @@ -232,11 +244,6 @@ type rec t<'value> = { maybeTypeFilter: option<(b, ~inputVar: string) => string>, @as("i") mutable isAsyncSchema: isAsyncSchema, - @as("d") - mutable // Use char to unsafely prevent Caml_option applications - definer?: char, - @as("c") - mutable definerCtx?: char, @as("m") metadataMap: dict, @as("parseOrThrow") @@ -271,7 +278,7 @@ and tagged = | Dict(t) | JSON({validated: bool}) and item = { - @as("t") + @as("s") schema: schema, @as("p") path: Path.t, @@ -279,8 +286,6 @@ and item = { location: string, @as("i") inlinedLocation: string, - @as("s") - symbol: Js.Types.symbol, } and builder = (b, ~input: val, ~selfSchema: schema, ~path: Path.t) => val and val = { @@ -1700,10 +1705,6 @@ let recursive = fn => { // maybeTypeFilter (placeholder->Obj.magic)["f"] = schema.maybeTypeFilter - // Don't allow destructuring for recursive schemas - schema.definer = None - schema.definerCtx = None - let initialParseOperationBuilder = schema.builder schema.builder = Builder.make((b, ~input, ~selfSchema, ~path) => { let bb = b->B.scope @@ -1978,11 +1979,11 @@ module Definition = { definition->Stdlib.Type.typeof === #object && definition !== %raw(`null`) let toConstant = (Obj.magic: t<'embeded> => unknown) - let toEmbeded = (Obj.magic: t<'embeded> => 'embeded) let toNode = (Obj.magic: t<'embeded> => node<'embeded>) @inline - let isEmbededItem = definition => (definition->toEmbeded).symbol === itemSymbol + let toEmbededItem = (definition: t<'embeded>): option => + definition->Obj.magic->Stdlib.Dict.unsafeGetOptionBySymbol(itemSymbol) } module Option = { @@ -2242,6 +2243,13 @@ module Object = { let getItems = (schema): array => (schema->classify->Obj.magic)["items"] let getOutputDefinition = schema => (schema->classify->Obj.magic)["definition"] + type outputItem = + | ExitNode + | Object({path: Path.t}) + | Tuple({path: Path.t}) + | Target({path: Path.t, item: item}) + | Constant({path: Path.t, value: unknown}) + module BuildCtx = { // type inputKind = | @as(0) Any | @as(1) Object | @as(2) Tuple @unboxed @@ -2286,35 +2294,38 @@ module Object = { let toOutputVal = (b, ~ctx: t, ~outputDefinition) => { let syncOutput = { let rec definitionToValue = (definition: Definition.t, ~outputPath) => { - switch ctx.outputs->Stdlib.WeakMap.get(definition->Definition.toEmbeded) { - | Some(val) => b->B.Val.inline(val) - | None => - if definition->Definition.isNode { - let node = definition->Definition.toNode - let isArray = Stdlib.Array.isArray(node) - let keys = node->Js.Dict.keys + if definition->Definition.isNode { + switch ctx.outputs->Stdlib.WeakMap.get( + definition->Definition.toEmbededItem->Stdlib.Option.unsafeUnwrap, + ) { + | Some(val) => b->B.Val.inline(val) + | None => { + let node = definition->Definition.toNode + let isArray = Stdlib.Array.isArray(node) + let keys = node->Js.Dict.keys - let codeRef = ref(isArray ? "[" : "{") - for idx in 0 to keys->Js.Array2.length - 1 { - let key = keys->Js.Array2.unsafe_get(idx) - let definition = node->Js.Dict.unsafeGet(key) - let output = - definition->definitionToValue( - ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), - ) - if idx !== 0 { - codeRef := codeRef.contents ++ "," + let codeRef = ref(isArray ? "[" : "{") + for idx in 0 to keys->Js.Array2.length - 1 { + let key = keys->Js.Array2.unsafe_get(idx) + let definition = node->Js.Dict.unsafeGet(key) + let output = + definition->definitionToValue( + ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), + ) + if idx !== 0 { + codeRef := codeRef.contents ++ "," + } + codeRef := + codeRef.contents ++ ( + isArray ? output : `${key->Stdlib.Inlined.Value.fromString}:${output}` + ) } - codeRef := - codeRef.contents ++ ( - isArray ? output : `${key->Stdlib.Inlined.Value.fromString}:${output}` - ) + codeRef.contents ++ (isArray ? "]" : "}") } - codeRef.contents ++ (isArray ? "]" : "}") - } else { - let constant = definition->Definition.toConstant - b->B.embed(constant) } + } else { + let constant = definition->Definition.toConstant + b->B.embed(constant) } } outputDefinition @@ -2333,11 +2344,76 @@ module Object = { ) } } + + let toOutputVal2 = (b, ~ctx: t, ~outputItems: array) => { + // let syncOutput = { + // let rec definitionToValue = (definition: Definition.t, ~outputPath) => { + // if definition->Definition.isNode { + // switch ctx.outputs->Stdlib.WeakMap.get( + // definition->Definition.toEmbededItem->Stdlib.Option.unsafeUnwrap, + // ) { + // | Some(val) => b->B.Val.inline(val) + // | None => { + // let node = definition->Definition.toNode + // let isArray = Stdlib.Array.isArray(node) + // let keys = node->Js.Dict.keys + + // let codeRef = ref(isArray ? "[" : "{") + // for idx in 0 to keys->Js.Array2.length - 1 { + // let key = keys->Js.Array2.unsafe_get(idx) + // let definition = node->Js.Dict.unsafeGet(key) + // let output = + // definition->definitionToValue( + // ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), + // ) + // if idx !== 0 { + // codeRef := codeRef.contents ++ "," + // } + // codeRef := + // codeRef.contents ++ ( + // isArray ? output : `${key->Stdlib.Inlined.Value.fromString}:${output}` + // ) + // } + // codeRef.contents ++ (isArray ? "]" : "}") + // } + // } + // } else { + // let constant = definition->Definition.toConstant + // b->B.embed(constant) + // } + // } + // outputDefinition + // ->(Obj.magic: unknown => Definition.t) + // ->definitionToValue(~outputPath=Path.empty) + // } + + let syncOutput = ref("") + let currentPath = ref(Path.empty) + + for idx in 0 to outputItems->Js.Array2.length - 1 { + let outputItem = outputItems->Js.Array2.unsafe_get(idx) + switch outputItem { + | Constant({path, value}) => b->B.embed(value) + | ExitNode => () + } + } + + if ctx.asyncOutputs === None { + b->B.val(syncOutput.contents) + } else { + let asyncOutputs = ctx.asyncOutputs->(Obj.magic: asyncOutputs => array) + b->B.asyncVal( + `Promise.all([${asyncOutputs + ->Js.Array2.map(val => b->B.Val.inline(val)) + ->Js.Array2.toString}]).then(a=>(${syncOutput.contents}))`, + ) + } + } } let typeFilter = (_b, ~inputVar) => `!${inputVar}||${inputVar}.constructor!==Object` - let rec processInputItems = (b: b, ~ctx: BuildCtx.t, ~input, ~schema, ~path) => { + let processInputItems = (b: b, ~ctx: BuildCtx.t, ~input, ~schema, ~path) => { let inputVar = b->B.Val.var(input) let items = schema->getItems @@ -2362,12 +2438,8 @@ module Object = { } let bb = b->B.scope - if isObject && schema.definer->Obj.magic { - bb->processInputItems(~ctx, ~input=itemInput, ~schema, ~path) - } else { - let itemOutput = bb->B.parse(~schema, ~input=itemInput, ~path) - b->BuildCtx.addItemOutput(~ctx, item, itemOutput) - } + let itemOutput = bb->B.parse(~schema, ~input=itemInput, ~path) + b->BuildCtx.addItemOutput(~ctx, item, itemOutput) // Parse literal fields first, because they are most often used as discriminants if isLiteral { @@ -2450,8 +2522,6 @@ module Object = { let itemSchema = item.schema if itemSchema->isLiteralSchema { b->B.embed(itemSchema->Literal.unsafeFromSchema->Literal.value) - } else if isRootObject && itemSchema.definer->Obj.magic { - schemaOutput(~isObject=true, ~schema=itemSchema, ~path=path->Path.concat(item.path)) } else { b->B.invalidOperation( ~path, @@ -2470,6 +2540,17 @@ module Object = { } } + let proxify = (item: item): 'a => + Stdlib.Object.immutableEmpty->Stdlib.Proxy.make({ + get: (~target as _, ~prop) => { + if prop === itemSymbol->Obj.magic { + item->Obj.magic + } else { + %raw(`void 0`) + } + }, + }) + let rec reverse = (~definition as inputDefinition, ~toItem=?) => () => { let original = %raw(`this`) let inputDefinition = inputDefinition->(Obj.magic: unknown => Definition.t) @@ -2497,8 +2578,8 @@ module Object = { let rec definitionToOutput = (definition: Definition.t, ~outputPath) => { if definition->Definition.isNode { - if definition->Definition.isEmbededItem { - let item = definition->Definition.toEmbeded + switch definition->Definition.toEmbededItem { + | Some(item) => switch embededOutputs->Stdlib.WeakMap.get(item) { | Some(embededOutput) => { let {schema} = item @@ -2532,16 +2613,17 @@ module Object = { embededOutputs->Stdlib.WeakMap.set(item, itemOutput)->ignore } } - } else { - let node = definition->Definition.toNode - let keys = node->Js.Dict.keys - for idx in 0 to keys->Js.Array2.length - 1 { - let key = keys->Js.Array2.unsafe_get(idx) - let definition = node->Js.Dict.unsafeGet(key) - definitionToOutput( - definition, - ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), - ) + | None => { + let node = definition->Definition.toNode + let keys = node->Js.Dict.keys + for idx in 0 to keys->Js.Array2.length - 1 { + let key = keys->Js.Array2.unsafe_get(idx) + let definition = node->Js.Dict.unsafeGet(key) + definitionToOutput( + definition, + ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), + ) + } } } } else { @@ -2573,32 +2655,56 @@ module Object = { and to = { (schema: t<'value>, definer: 'value => 'variant): t<'variant> => { let schema = schema->toUnknown - if schema.definer->Obj.magic { - factory((ctx => definer((schema.definer->Obj.magic)(ctx)))->Obj.magic) - } else { - let item: item = { - schema, - path: Path.empty, - location: "", - inlinedLocation: `""`, - symbol: itemSymbol, - } - let definition: unknown = definer(item->Obj.magic)->Obj.magic - makeSchema( - ~name=schema.name, - ~tagged=schema.tagged, - ~builder=Builder.make((b, ~input, ~selfSchema, ~path) => { - let ctx = BuildCtx.make(~selfSchema) - let itemOutput = b->B.parse(~schema, ~input, ~path) - b->BuildCtx.addItemOutput(~ctx, item, itemOutput) - b->BuildCtx.toOutputVal(~ctx, ~outputDefinition=definition) - }), - ~maybeTypeFilter=schema.maybeTypeFilter, - ~metadataMap=schema.metadataMap, - ~reverse=reverse(~definition, ~toItem=item), - ) + let item: item = { + schema, + path: Path.empty, + location: "", + inlinedLocation: `""`, + } + let definition: unknown = definer(item->proxify)->Obj.magic + + let outputItems: array = [] + + let rec traverseDefinition = (definition: Definition.t, ~path) => { + if definition->Definition.isNode { + switch definition->Definition.toEmbededItem { + | Some(item) => outputItems->Js.Array2.push(Target({path, item}))->ignore + | None => { + let node = definition->Definition.toNode + let isArray = Stdlib.Array.isArray(node) + outputItems + ->Js.Array2.push(isArray ? Tuple({path: path}) : Object({path: path})) + ->ignore + let keys = node->Js.Dict.keys + for idx in 0 to keys->Js.Array2.length - 1 { + let key = keys->Js.Array2.unsafe_get(idx) + let definition = node->Js.Dict.unsafeGet(key) + definition->traverseDefinition(~path=Path.concat(path, Path.fromLocation(key))) + } + outputItems->Js.Array2.push(ExitNode)->ignore + } + } + } else { + let constant = definition->Definition.toConstant + outputItems->Js.Array2.push(Constant({path, value: constant}))->ignore + } } + definition->Obj.magic->traverseDefinition(~path=Path.empty) + + makeSchema( + ~name=schema.name, + ~tagged=schema.tagged, + ~builder=Builder.make((b, ~input, ~selfSchema, ~path) => { + let ctx = BuildCtx.make(~selfSchema) + let itemOutput = b->B.parse(~schema, ~input, ~path) + b->BuildCtx.addItemOutput(~ctx, item, itemOutput) + b->BuildCtx.toOutputVal(~ctx, ~outputDefinition=definition) + }), + ~maybeTypeFilter=schema.maybeTypeFilter, + ~metadataMap=schema.metadataMap, + ~reverse=reverse(~definition, ~toItem=item), + ) } } and factory: @@ -2609,11 +2715,11 @@ module Object = { let ctx = { let flatten = schema => { - if schema.definer->Obj.magic { - (schema.definer->Obj.magic)(%raw(`this`)) - } else { - InternalError.panic(`The ${schema.name()} schema can't be flattened`) - } + // if schema.definer->Obj.magic { + // (schema.definer->Obj.magic)(%raw(`this`)) + // } else { + InternalError.panic(`The ${schema.name()} schema can't be flattened`) + // } } let field: @@ -2622,36 +2728,35 @@ module Object = { let schema = schema->toUnknown let inlinedLocation = fieldName->Stdlib.Inlined.Value.fromString switch fields->Stdlib.Dict.unsafeGetOption(fieldName) { - | Some(item: item) => - if item.schema.definer->Obj.magic && schema.definer->Obj.magic { - (schema.definer->Obj.magic)(item.schema.definerCtx->Obj.magic)->( - Obj.magic: unknown => value - ) - } else { - InternalError.panic( - `The field ${inlinedLocation} defined twice with incompatible schemas`, - ) - } + | Some(_item: item) => + // if item.schema.definer->Obj.magic && schema.definer->Obj.magic { + // (schema.definer->Obj.magic)(item.schema.definerCtx->Obj.magic)->( + // Obj.magic: unknown => value + // ) + // } else { + InternalError.panic( + `The field ${inlinedLocation} defined twice with incompatible schemas`, + ) + // } | None => { - let schema = if schema.definer->Obj.magic { - factory(schema.definer->Obj.magic) - } else { - schema - } + // let schema = if schema.definer->Obj.magic { + // factory(schema.definer->Obj.magic) + // } else { + // schema + // } let item: item = { schema, location: fieldName, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } fields->Js.Dict.set(fieldName, item) items->Js.Array2.push(item)->ignore - if schema.definer->Obj.magic { - schema->getOutputDefinition->(Obj.magic: unknown => value) - } else { - item->(Obj.magic: item => value) - } + // if schema.definer->Obj.magic { + // schema->getOutputDefinition->(Obj.magic: unknown => value) + // } else { + item->proxify + // } } } } @@ -2669,17 +2774,17 @@ module Object = { (fieldName, nestedFieldName, schema) => { let schema = schema->toUnknown switch fields->Stdlib.Dict.unsafeGetOption(fieldName) { - | Some(item: item) => - if item.schema.definer->Obj.magic { - (item.schema.definerCtx->(Obj.magic: option => ctx)).field( - nestedFieldName, - schema, - )->(Obj.magic: unknown => value) - } else { - InternalError.panic( - `The field ${fieldName->Stdlib.Inlined.Value.fromString} defined twice with incompatible schemas`, - ) - } + | Some(_item: item) => + // if item.schema.definer->Obj.magic { + // (item.schema.definerCtx->(Obj.magic: option => ctx)).field( + // nestedFieldName, + // schema, + // )->(Obj.magic: unknown => value) + // } else { + InternalError.panic( + `The field ${fieldName->Stdlib.Inlined.Value.fromString} defined twice with incompatible schemas`, + ) + // } | None => field(fieldName, factory(s => s.field(nestedFieldName, schema)))->( Obj.magic: unknown => value @@ -2713,8 +2818,8 @@ module Object = { maybeTypeFilter: Some(typeFilter), name, metadataMap: Metadata.Map.empty, - definer: definer->Obj.magic, - definerCtx: ctx->Obj.magic, + // definer: definer->Obj.magic, + // definerCtx: ctx->Obj.magic, parseOrRaise: initialParseOrRaise, serializeToUnknownOrRaise: initialSerializeToUnknownOrRaise, serializeOrRaise: initialSerializeOrRaise, @@ -2741,8 +2846,8 @@ module Object = { maybeTypeFilter: schema.maybeTypeFilter, isAsyncSchema: schema.isAsyncSchema, metadataMap: schema.metadataMap, - definer: ?schema.definer, - definerCtx: ?schema.definerCtx, + // definer: ?schema.definer, + // definerCtx: ?schema.definerCtx, parseOrRaise: initialParseOrRaise, serializeToUnknownOrRaise: initialSerializeToUnknownOrRaise, serializeOrRaise: initialSerializeOrRaise, @@ -2800,10 +2905,9 @@ module Tuple = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) - item->(Obj.magic: item => value) + item->Object.proxify } } @@ -2829,7 +2933,6 @@ module Tuple = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) } @@ -3480,24 +3583,24 @@ module Schema = { let location = idx->Js.Int.toString let inlinedLocation = `"${location}"` let schema = node->Js.Array2.unsafe_get(idx)->definitionToSchema + let path = inlinedLocation->Path.fromInlinedLocation let item: item = { schema, location, inlinedLocation, - path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, + path, } items->Js.Array2.unsafe_set(idx, item) if !isTransformed.contents && schema !== schema.reverse() { isTransformed := true } } - let definition = items->(Obj.magic: array => unknown) + let definition = items->Js.Array2.map(Object.proxify)->(Obj.magic: array => unknown) makeSchema( ~name=Tuple.name, ~tagged=Tuple({ items, - definition, + definition, // FIXME: stop passing definition here }), ~builder=Object.builder, ~maybeTypeFilter=Some(Tuple.typeFilter(~length=items->Js.Array2.length)), @@ -3508,6 +3611,7 @@ module Schema = { let node = definition->(Obj.magic: unknown => dict) let items = [] let fields = Js.Dict.empty() + let definition = Js.Dict.empty() let fieldNames = node->Js.Dict.keys let isTransformed = ref(false) for idx in 0 to fieldNames->Js.Array2.length - 1 { @@ -3519,15 +3623,15 @@ module Schema = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) fields->Js.Dict.set(location, item) + definition->Js.Dict.set(location, item->Object.proxify) // FIXME: remove if !isTransformed.contents && schema !== schema.reverse() { isTransformed := true } } - let definition = fields->(Obj.magic: dict => unknown) + let definition = definition->(Obj.magic: dict => unknown) makeSchema( ~name=Object.name, ~tagged=Object({ diff --git a/src/S_Core.resi b/src/S_Core.resi index 2319a7c..0a07d0b 100644 --- a/src/S_Core.resi +++ b/src/S_Core.resi @@ -55,7 +55,7 @@ and tagged = | Dict(t) | JSON({validated: bool}) and item = { - @as("t") + @as("s") schema: schema, @as("p") path: Path.t, @@ -63,8 +63,6 @@ and item = { location: string, @as("i") inlinedLocation: string, - @as("s") - symbol: Js.Types.symbol, } and schema<'value> = t<'value> and error = private {operation: operation, code: errorCode, path: Path.t}