FEATURE: More robust schema matching.

* Fixed some weirdness with partial matching in shaped.
* Added an all matcher for bundling multiple partial matches together.
This commit is contained in:
Jeremy Wall 2019-04-17 22:17:06 -05:00
parent 643b597e35
commit 524f85102f
2 changed files with 80 additions and 13 deletions

View File

@ -23,12 +23,12 @@ let must = func (m, msg) => select m, fail msg, {
true = m,
};
// Any does a boolean match against a set of allowed shapes for a type.
// All does a boolean match against all of set of allowed shapes for a type.
// This module uses the shape module below so the same rules for checking
// the types against the source value apply for each example in the list.
//
// This module does not do partial matches for the shapes.
let any = module {
// This module does partial matches for the shapes.
let all = module {
// The source value to check.
val=NULL,
// The set of allowed type shapes it can be.
@ -37,7 +37,30 @@ let any = module {
let schema = mod.pkg();
let reducer = func (acc, t) => acc{
ok = acc.ok || (schema.shaped{val=acc.val, shape=t, partial=false}),
ok = acc.ok && (schema.shaped{val=acc.val, shape=t, partial=true}),
};
let any = func (val, types) => reduce(reducer, {ok=true, val=val}, types);
let result = any(mod.val, mod.types).ok;
};
// Any does a boolean match against a set of allowed shapes for a type.
// This module uses the shape module below so the same rules for checking
// the types against the source value apply for each example in the list.
//
// This module does not do partial matches for the shapes by default.
let any = module {
// The source value to check.
val=NULL,
// The set of allowed type shapes it can be.
types=[],
// Whether to do partial matches for the shapes
partial=false
} => (result) {
let schema = mod.pkg();
let reducer = func (acc, t) => acc{
ok = acc.ok || (schema.shaped{val=acc.val, shape=t, partial=mod.partial}),
};
let any = func (val, types) => reduce(reducer, {ok=false, val=val}, types);
@ -74,12 +97,24 @@ let shaped = module {
let tuple_handler = func (acc, name, value) => acc{
ok = select (name) in acc.shape, mod.partial, {
true = schema.shaped{val=value, shape=acc.shape.(name)},
true = schema.shaped{val=value, shape=acc.shape.(name), partial=mod.partial},
},
};
let shape_tuple_handler = func (acc, name, value) => acc{
ok = select (name) in acc.val, false, {
true = schema.shaped{val=value, shape=acc.val.(name), partial=false},
},
};
let match_shape_fields = func(val, shape) => reduce(
shape_tuple_handler, {val=val, ok=false}, shape).ok;
let match_tuple_fields = func(val, shape) =>
reduce(tuple_handler, {shape=shape, ok=false}, val).ok;
let list_handler = func(acc, value) => acc{
ok = false || schema.any{val=value, types=acc.shape},
ok = acc.ok && schema.any{val=value, types=acc.shape},
};
let result =select schema.base_type_of(mod.val), false, {
@ -88,9 +123,11 @@ let shaped = module {
float = simple_handler(mod.val, mod.shape),
bool = simple_handler(mod.val, mod.shape),
null = simple_handler(mod.val, mod.shape),
tuple = (mod.shape is "tuple") && reduce(tuple_handler, {shape=mod.shape, ok=false}, mod.val).ok,
list = select mod.shape == [], true, {
false = reduce(list_handler, {shape=mod.shape, ok=false}, mod.val).ok,
tuple = (mod.shape is "tuple") &&
match_shape_fields(mod.val, mod.shape) &&
match_tuple_fields(mod.val, mod.shape),
list = (mod.shape is "list") && select mod.shape == [], true, {
false = reduce(list_handler, {shape=mod.shape, ok=true}, mod.val).ok,
},
func = simple_handler(mod.val, mod.shape),
module = simple_handler(mod.val, mod.shape),

View File

@ -131,12 +131,42 @@ assert t.ok{
desc="inner list with valid types matches empty list shape",
};
assert t.not_ok{
test = schema.shaped{val={list=[1, "foo"]}, shape={list="foo"}},
desc="inner list with with non list shape does not match",
};
assert t.not_ok{
test = schema.shaped{val={foo="bar"}, shaped=1.0},
desc = "a tuple is not a float",
};
assert t.not_ok{
test = schema.any{val={foo="bar", quux="baz"}, types=[1.0, {foo="bar", qux="baz"}]},
desc = "any doesn't match against missing fields for tuples.",
test = schema.any{val={foo="bar", quux="baz"}, types=[1.0, {foo="bar"}]},
desc = "any doesn't match against missing fields for tuples for value tuple.",
};
assert t.ok{
test = schema.any{val={foo="bar", quux="baz"}, types=[1.0, {foo="bar"}], partial=true},
desc = "any can do partial matching against missing fields for tuples shapes.",
};
assert t.not_ok{
test = schema.any{val={foo="bar"}, types=[1.0, {foo="", quux=""}]},
desc = "any does match against missing fields for tuples shape tuple.",
};
assert t.not_ok{
test = schema.shaped{val={foo="bar", baz="quux"}, shape={bear=""}, partial=true},
desc = "even with partial all of the shape fields must be present.",
};
assert t.ok{
test = schema.all{val={foo="bar", baz="quux"}, types=[{foo=""}, {baz=""}]},
desc = "all enforces that all of the valid types are partial matches (success)",
};
assert t.not_ok{
test = schema.all{val={foo="bar", baz="quux"}, types=[{foo=""}, {baz=""}, {quux=""}]},
desc = "all enforces that all of the valid shapes must be partial matches (fail)",
};