Skip to content

Commit 1f3ee58

Browse files
committed
Fix #150
1 parent 8a8e7d0 commit 1f3ee58

File tree

4 files changed

+147
-105
lines changed

4 files changed

+147
-105
lines changed

packages/sury/src/Sury.res

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,6 +2619,8 @@ let never = base(Never)
26192619
never.refiner = Some(neverBuilder)
26202620
let never: t<never> = never->castToPublic
26212621

2622+
let nestedLoc = "BS_PRIVATE_NESTED_SOME_NONE"
2623+
26222624
module Union = {
26232625
@unboxed
26242626
type itemCode = Single(string) | Multiple(array<string>)
@@ -2765,8 +2767,16 @@ module Union = {
27652767
: (tag :> string)
27662768
switch byKey.contents->X.Dict.getUnsafeOption(key) {
27672769
| Some(arr) =>
2768-
// There can only be one valid. Dedupe
27692770
if (
2771+
tagFlag->Flag.unsafeHas(TagFlag.object) &&
2772+
schema.properties->X.Option.getUnsafe->Dict.has(nestedLoc)
2773+
) {
2774+
// This is a special case for https://github.com/DZakh/sury/issues/150
2775+
// When nested option goes together with an empty object schema
2776+
// Since we put None case check second, we need to change priority here.
2777+
arr->Js.Array2.unshift(schema)->ignore
2778+
} else if (
2779+
// There can only be one valid. Dedupe
27702780
!(
27712781
tagFlag->Flag.unsafeHas(
27722782
TagFlag.undefined->Flag.with(TagFlag.null)->Flag.with(TagFlag.nan),
@@ -3100,8 +3110,6 @@ module Union = {
31003110
module Option = {
31013111
type default = Value(unknown) | Callback(unit => unknown)
31023112

3103-
let nestedLoc = "BS_PRIVATE_NESTED_SOME_NONE"
3104-
31053113
let nestedOption = {
31063114
let nestedNone = () => {
31073115
let itemSchema = Literal.parse(0)

packages/sury/src/Sury.res.mjs

Lines changed: 103 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,8 @@ let never = new Schema("never");
16931693

16941694
never.refiner = neverBuilder;
16951695

1696+
let nestedLoc = "BS_PRIVATE_NESTED_SOME_NONE";
1697+
16961698
function getItemCode(b, schema, input, output, deopt, path) {
16971699
try {
16981700
let globalFlag = b.g.o;
@@ -1784,7 +1786,9 @@ function refiner(b, input, selfSchema, path) {
17841786
let key = tagFlag & 8192 ? schema.class.name : tag;
17851787
let arr = byKey[key];
17861788
if (arr !== undefined) {
1787-
if (!(tagFlag & 2096)) {
1789+
if (tagFlag & 64 && nestedLoc in schema.properties) {
1790+
arr.unshift(schema);
1791+
} else if (!(tagFlag & 2096)) {
17881792
arr.push(schema);
17891793
}
17901794

@@ -2008,8 +2012,6 @@ function factory(schemas) {
20082012
throw new Error("[Sury] S.union requires at least one item");
20092013
}
20102014

2011-
let nestedLoc = "BS_PRIVATE_NESTED_SOME_NONE";
2012-
20132015
function nestedNone() {
20142016
let itemSchema = parse$1(0);
20152017
let item = {
@@ -2670,70 +2672,6 @@ function schemaRefiner(b, input, selfSchema, path) {
26702672
}
26712673
}
26722674

2673-
function definitionToRitem(definition, path, ritemsByItemPath) {
2674-
if (typeof definition !== "object" || definition === null) {
2675-
return {
2676-
k: 1,
2677-
p: path,
2678-
s: copyWithoutCache(parse$1(definition))
2679-
};
2680-
}
2681-
let item = definition[itemSymbol];
2682-
if (item !== undefined) {
2683-
let ritemSchema = copyWithoutCache(getOutputSchema(item.schema));
2684-
((delete ritemSchema.serializer));
2685-
let ritem = {
2686-
k: 0,
2687-
p: path,
2688-
s: ritemSchema
2689-
};
2690-
item.r = ritem;
2691-
ritemsByItemPath[getFullDitemPath(item)] = ritem;
2692-
return ritem;
2693-
}
2694-
if (Array.isArray(definition)) {
2695-
let items = [];
2696-
for (let idx = 0, idx_finish = definition.length; idx < idx_finish; ++idx) {
2697-
let location = idx.toString();
2698-
let inlinedLocation = "\"" + location + "\"";
2699-
let ritem$1 = definitionToRitem(definition[idx], path + ("[" + inlinedLocation + "]"), ritemsByItemPath);
2700-
let item_schema = ritem$1.s;
2701-
let item$1 = {
2702-
schema: item_schema,
2703-
location: location
2704-
};
2705-
items[idx] = item$1;
2706-
}
2707-
let mut = new Schema("array");
2708-
return {
2709-
k: 2,
2710-
p: path,
2711-
s: (mut.items = items, mut.additionalItems = "strict", mut.serializer = neverBuilder, mut)
2712-
};
2713-
}
2714-
let fieldNames = Object.keys(definition);
2715-
let properties = {};
2716-
let items$1 = [];
2717-
for (let idx$1 = 0, idx_finish$1 = fieldNames.length; idx$1 < idx_finish$1; ++idx$1) {
2718-
let location$1 = fieldNames[idx$1];
2719-
let inlinedLocation$1 = fromString(location$1);
2720-
let ritem$2 = definitionToRitem(definition[location$1], path + ("[" + inlinedLocation$1 + "]"), ritemsByItemPath);
2721-
let item_schema$1 = ritem$2.s;
2722-
let item$2 = {
2723-
schema: item_schema$1,
2724-
location: location$1
2725-
};
2726-
items$1[idx$1] = item$2;
2727-
properties[location$1] = item_schema$1;
2728-
}
2729-
let mut$1 = new Schema("object");
2730-
return {
2731-
k: 2,
2732-
p: path,
2733-
s: (mut$1.items = items$1, mut$1.properties = properties, mut$1.additionalItems = globalConfig.a, mut$1.serializer = neverBuilder, mut$1)
2734-
};
2735-
}
2736-
27372675
function definitionToSchema(definition) {
27382676
if (typeof definition !== "object" || definition === null) {
27392677
return parse$1(definition);
@@ -2860,42 +2798,67 @@ function nested(fieldName) {
28602798
return ctx$1;
28612799
}
28622800

2863-
function advancedBuilder(definition, flattened) {
2864-
return (b, input, selfSchema, path) => {
2865-
let isFlatten = b.g.o & 64;
2866-
let outputs = isFlatten ? input.properties : ({});
2867-
if (!isFlatten) {
2868-
let items = selfSchema.items;
2869-
for (let idx = 0, idx_finish = items.length; idx < idx_finish; ++idx) {
2870-
let match = items[idx];
2871-
let location = match.location;
2872-
let itemInput = get(b, input, location);
2873-
let inlinedLocation = inlineLocation(b, location);
2874-
let path$1 = path + ("[" + inlinedLocation + "]");
2875-
outputs[location] = parse(b, match.schema, itemInput, path$1);
2876-
}
2877-
objectStrictModeCheck(b, input, items, selfSchema, path);
2878-
}
2879-
if (flattened !== undefined) {
2880-
let prevFlag = b.g.o;
2881-
b.g.o = prevFlag | 64;
2882-
for (let idx$1 = 0, idx_finish$1 = flattened.length; idx$1 < idx_finish$1; ++idx$1) {
2883-
let item = flattened[idx$1];
2884-
outputs[item.i] = parse(b, item.schema, input, path);
2885-
}
2886-
b.g.o = prevFlag;
2801+
function definitionToRitem(definition, path, ritemsByItemPath) {
2802+
if (typeof definition !== "object" || definition === null) {
2803+
return {
2804+
k: 1,
2805+
p: path,
2806+
s: copyWithoutCache(parse$1(definition))
2807+
};
2808+
}
2809+
let item = definition[itemSymbol];
2810+
if (item !== undefined) {
2811+
let ritemSchema = copyWithoutCache(getOutputSchema(item.schema));
2812+
((delete ritemSchema.serializer));
2813+
let ritem = {
2814+
k: 0,
2815+
p: path,
2816+
s: ritemSchema
2817+
};
2818+
item.r = ritem;
2819+
ritemsByItemPath[getFullDitemPath(item)] = ritem;
2820+
return ritem;
2821+
}
2822+
if (Array.isArray(definition)) {
2823+
let items = [];
2824+
for (let idx = 0, idx_finish = definition.length; idx < idx_finish; ++idx) {
2825+
let location = idx.toString();
2826+
let inlinedLocation = "\"" + location + "\"";
2827+
let ritem$1 = definitionToRitem(definition[idx], path + ("[" + inlinedLocation + "]"), ritemsByItemPath);
2828+
let item_schema = ritem$1.s;
2829+
let item$1 = {
2830+
schema: item_schema,
2831+
location: location
2832+
};
2833+
items[idx] = item$1;
28872834
}
2888-
let getItemOutput = item => {
2889-
switch (item.k) {
2890-
case 0 :
2891-
return outputs[item.location];
2892-
case 1 :
2893-
return get(b, getItemOutput(item.of), item.location);
2894-
case 2 :
2895-
return outputs[item.i];
2896-
}
2835+
let mut = new Schema("array");
2836+
return {
2837+
k: 2,
2838+
p: path,
2839+
s: (mut.items = items, mut.additionalItems = "strict", mut.serializer = neverBuilder, mut)
28972840
};
2898-
return definitionToOutput(b, definition, getItemOutput, selfSchema.to);
2841+
}
2842+
let fieldNames = Object.keys(definition);
2843+
let properties = {};
2844+
let items$1 = [];
2845+
for (let idx$1 = 0, idx_finish$1 = fieldNames.length; idx$1 < idx_finish$1; ++idx$1) {
2846+
let location$1 = fieldNames[idx$1];
2847+
let inlinedLocation$1 = fromString(location$1);
2848+
let ritem$2 = definitionToRitem(definition[location$1], path + ("[" + inlinedLocation$1 + "]"), ritemsByItemPath);
2849+
let item_schema$1 = ritem$2.s;
2850+
let item$2 = {
2851+
schema: item_schema$1,
2852+
location: location$1
2853+
};
2854+
items$1[idx$1] = item$2;
2855+
properties[location$1] = item_schema$1;
2856+
}
2857+
let mut$1 = new Schema("object");
2858+
return {
2859+
k: 2,
2860+
p: path,
2861+
s: (mut$1.items = items$1, mut$1.properties = properties, mut$1.additionalItems = globalConfig.a, mut$1.serializer = neverBuilder, mut$1)
28992862
};
29002863
}
29012864

@@ -2989,6 +2952,45 @@ function definitionToTarget(definition, to, flattened) {
29892952
return mut;
29902953
}
29912954

2955+
function advancedBuilder(definition, flattened) {
2956+
return (b, input, selfSchema, path) => {
2957+
let isFlatten = b.g.o & 64;
2958+
let outputs = isFlatten ? input.properties : ({});
2959+
if (!isFlatten) {
2960+
let items = selfSchema.items;
2961+
for (let idx = 0, idx_finish = items.length; idx < idx_finish; ++idx) {
2962+
let match = items[idx];
2963+
let location = match.location;
2964+
let itemInput = get(b, input, location);
2965+
let inlinedLocation = inlineLocation(b, location);
2966+
let path$1 = path + ("[" + inlinedLocation + "]");
2967+
outputs[location] = parse(b, match.schema, itemInput, path$1);
2968+
}
2969+
objectStrictModeCheck(b, input, items, selfSchema, path);
2970+
}
2971+
if (flattened !== undefined) {
2972+
let prevFlag = b.g.o;
2973+
b.g.o = prevFlag | 64;
2974+
for (let idx$1 = 0, idx_finish$1 = flattened.length; idx$1 < idx_finish$1; ++idx$1) {
2975+
let item = flattened[idx$1];
2976+
outputs[item.i] = parse(b, item.schema, input, path);
2977+
}
2978+
b.g.o = prevFlag;
2979+
}
2980+
let getItemOutput = item => {
2981+
switch (item.k) {
2982+
case 0 :
2983+
return outputs[item.location];
2984+
case 1 :
2985+
return get(b, getItemOutput(item.of), item.location);
2986+
case 2 :
2987+
return outputs[item.i];
2988+
}
2989+
};
2990+
return definitionToOutput(b, definition, getItemOutput, selfSchema.to);
2991+
};
2992+
}
2993+
29922994
function shape(schema, definer) {
29932995
return updateOutput(schema, mut => {
29942996
let ditem = {

packages/sury/tests/S_null_test.res

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,35 @@ test("Serializes Some(None) to null for null nested in null", t => {
168168
`i=>{if(i===void 0){i=null}else if(typeof i==="object"&&i&&i["BS_PRIVATE_NESTED_SOME_NONE"]===0){i=null}return i}`,
169169
)
170170
})
171+
172+
// https://github.com/DZakh/sury/issues/150
173+
module OuterRecord = {
174+
module Inner = {
175+
type t = {k?: option<int>}
176+
177+
let schema = S.schema((s): t => {
178+
k: ?s.matches(S.option(S.null(S.int))),
179+
})
180+
}
181+
182+
type t = {record?: option<Inner.t>}
183+
184+
let schema = S.schema(s => {
185+
record: ?s.matches(S.option(S.null(Inner.schema))),
186+
})
187+
188+
test("Record schema with optional nullable field", t => {
189+
let record = {record: None}
190+
191+
t->Assert.deepEqual(record, %raw(`{ record: { BS_PRIVATE_NESTED_SOME_NONE: 0 } }`))
192+
t->Assert.deepEqual(record->S.reverseConvertOrThrow(schema), %raw(`{ record: null }`))
193+
t->Assert.deepEqual(record->S.reverseConvertToJsonStringOrThrow(schema), `{"record":null}`)
194+
195+
Js.log(schema->S.reverse)
196+
t->U.assertCompiledCode(
197+
~schema,
198+
~op=#ReverseConvert,
199+
`i=>{let v0=i["record"];if(typeof v0==="object"&&v0){if(v0["BS_PRIVATE_NESTED_SOME_NONE"]===0){v0=null}else{try{let v1=v0["k"];if(typeof v1==="object"&&v1&&v1["BS_PRIVATE_NESTED_SOME_NONE"]===0){v1=null}v0={"k":v1,}}catch(e1){}}}return {"record":v0,}}`,
200+
)
201+
})
202+
}

packages/sury/tests/S_option_test.res

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ test("Triple nested option support", t => {
197197
t->U.assertCompiledCode(
198198
~schema,
199199
~op=#ReverseConvert,
200-
`i=>{if(typeof i==="object"&&i){if(i["BS_PRIVATE_NESTED_SOME_NONE"]===0){i=void 0}else if(i["BS_PRIVATE_NESTED_SOME_NONE"]===1){i=void 0}}return i}`,
200+
`i=>{if(typeof i==="object"&&i){if(i["BS_PRIVATE_NESTED_SOME_NONE"]===1){i=void 0}else if(i["BS_PRIVATE_NESTED_SOME_NONE"]===0){i=void 0}}return i}`,
201201
)
202202
})
203203

0 commit comments

Comments
 (0)