Commit d912ea30 authored by Eric Myhre's avatar Eric Myhre

Drop code of an incorrect attempt at Validate().

Comments added addressing some of the root issues of why, and how to
try again more correctly next time.  (At least one open question
has been found that might take a few days to resolve.)

This commit happens now because they code being removed refered to
`schema.Type.ReprKind()` a lot, and that's the real issue I'm focused
on at the moment -- that's also going away in the next commit or so.
There's significantly more subtlety to the type-vs-representation
duality than the existence of such a method could accurately describe.
parent ecfee940
package schema package schema
import ( /*
"fmt" Okay, so. There are several fun considerations for a "validate" method.
"path"
---
"github.com/ipld/go-ipld-prime"
) There's two radically different approaches to "validate"/"reify":
// FUTURE: we also want something *almost* identical to this Validate method, - Option 1: Look at the schema.Type info and check if a data node seems
// but returning a `typed.Node` in the case of no error. to match it -- recursing on the type info.
// (Such a method would go in the same package as `typed.Node`, presumably.) - Option 2: Use the schema.Type{}.RepresentationNodeBuilder() to feed data
// How do we avoid writing this method twice? into it -- recursing on what the nodebuilder already expresses.
// Maybe both a Validate and Reify method belong in `typed` package,
// and Validate just returns less? (Option 2 also need to take a `memStorage ipld.NodeBuilder` param, btw,
// No... Reify should probably short-circuit sooner? for handling all the cases where we *aren't* doing codegen.)
// Unclear. Guess first step is that we need to decide the intended UX!
Option 1 provides a little more opportunity for returning multiple errors.
func Validate(ts TypeSystem, t Type, node ipld.Node) []error { Option 2 will generally have a hard time with that (nodebuilers are not
return validate(ts, t, node, "/") necessarily in a valid state after their first error encounter).
}
As a result of having these two options at all, we may indeed end up with
// review: 'ts' param might not actually be necessary; everything relevant can be reached from t so far. at least two very different functions -- despite seeming to do similar
func validate(ts TypeSystem, t Type, node ipld.Node, pth string) []error { things, their interior will radically diverge.
switch t2 := t.(type) {
case TypeBool: ---
if node.ReprKind() != ipld.ReprKind_Bool {
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())} We may also need to consider distinct reification paths: we may want one
} that returns a new node tree which is eagerly converted to typed.Node
return nil recursively; and another that returns a lazyNode which wraps things
case TypeString: with their typed node constraints only as they're requested.
if node.ReprKind() != ipld.ReprKind_String { (Note that the latter would have interesting implications for any code
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())} which has expectations about pointer equality consistency.)
}
return nil ---
case TypeBytes:
if node.ReprKind() != ipld.ReprKind_Bytes { A further fun issue which needs consideration: well, I'll just save a snip
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())} of prospective docs I wrote while trying to iterate on these functions:
}
return nil // Note that using Validate on a node that's already a typed.Node is likely
case TypeInt: // to be nonsensical. In many schemas, the typed.Node tree is actually a
if node.ReprKind() != ipld.ReprKind_Int { // different depth than its representational tree (e.g. unions can cause this),
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())}
} ... and that's ... that's a fairly sizable issue that needs resolving.
return nil There's a couple of different ways to handle some of the behaviors around
case TypeFloat: unions, and some of them make the tradeoff described above, and I'm really
if node.ReprKind() != ipld.ReprKind_Float { unsure if all the implications have been sussed out yet. We should defer
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())} writing code that depends on this issue until gathering some more info.
}
return nil ---
case TypeMap:
if node.ReprKind() != ipld.ReprKind_Map { One more note: about returning multiple errors from a Validate function:
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())} there's an upper bound of the utility of the thing. Going farther than the
} first parse error is nice, but it will still hit limits: for example,
errs := []error(nil) upon encountering a union and failing to match it, we can't generally
for itr := node.MapIterator(); !itr.Done(); { produce further errors from anywhere deeper in the tree without them being
k, v, err := itr.Next() combinatorial "if previous juncture X was type Y, then..." nonsense.
if err != nil { (This applies to all recursive kinds to some degree, but it's especially
return []error{err} rough with unions. For most of the others, it's flatly a missing field,
} or an excessive field, or a leaf error; with unions it can be hard to tell.)
// FUTURE: if KeyType is an enum rather than string, do membership check.
ks, _ := k.AsString() ---
if v.IsNull() {
if !t2.ValueIsNullable() { And finally: both "Validate" and "Reify" methods might actually belong
errs = append(errs, fmt.Errorf("Schema match failed: map at path %q contains unpermitted null in key %q", pth, ks)) in the typed.Node package -- if they make *any* reference to `typed.Node`,
} then they have no choice (otherwise, cyclic imports would occur).
} else { If we make a "Validate" that works purely on the schema.Type info, and
errs = append(errs, validate(ts, t2.ValueType(), v, path.Join(pth, ks))...) returns *only* errors: only then we can have it in the schema package.
}
} */
return errs
case TypeList:
case TypeLink:
// TODO interesting case: would need resolver to keep checking.
case TypeUnion:
// TODO *several* interesting errors
case TypeStruct:
switch t2.tupleStyle {
case false: // as map!
if node.ReprKind() != ipld.ReprKind_Map {
return []error{fmt.Errorf("Schema match failed: expected type %q (which is kind %v) at path %q, but found kind %v", t2.Name(), t.ReprKind(), pth, node.ReprKind())}
}
// TODO loop over em
// TODO REVIEW order strictness questions?
case true: // as array!
}
case TypeEnum:
// TODO another interesting error
}
return nil
}
package schema
import (
"testing"
. "github.com/warpfork/go-wish"
"github.com/ipld/go-ipld-prime/impl/free"
)
func TestSimpleTypes(t *testing.T) {
t.Run("string alone", func(t *testing.T) {
n1, _ := ipldfree.NodeBuilder().CreateString("asdf")
t1 := TypeString{
anyType{name: "Foo"},
}
Wish(t,
Validate(TypeSystem{}, t1, n1),
ShouldEqual, []error(nil))
})
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment