Commit c8e3e1d4 authored by Eric Myhre's avatar Eric Myhre

Reboot typed dir. Now with child packages.

See previous commit for more discussion of the child packages to come,
or the schema.md file in the docs dir, which describes similar.

Several large todos in comments.

Quite importantly: typed.Node is now an **interface**, not a concrete
struct itself.  The godoc comment should explain this nicely; long
story short, this is going to be relevant when we get to codegen and
other advanced form of native integration.
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent 63016b7f
package typed
var (
// Prelude types
tString = TypeString{
Name: "String",
}
// User's types
/*
struct Foo {
f1 String
f2 [nullable String]
f3 optional String
}
*/
tFoo = TypeObject{
Name: "Foo",
Fields: []ObjectField{
{"f1", tString, false, true},
{"f2", TypeList{
Name: "", // "[nullable String]" can be calculated.
Anon: true,
ValueType: tString,
ValueNullable: true,
}, false, false},
{"f3", tString, true, false},
},
}
// The Universe
example = Universe{
tFoo.Name: tFoo,
}
)
package typed
// Kind is our type level kind. It includes "object", "union", and other
// advanced concepts. "map" at this layer also contains additional constraints;
// it must be a single type of element.
type Kind uint8
// REVIEW: unclear if this is needed. Can switch on `Type.(type)`.
const (
Kind_Invalid = 0
Kind_Bool = 'b'
Kind_String = 's'
Kind_Bytes = 'x'
Kind_Int = 'i'
Kind_Float = 'f'
Kind_Map = '{'
Kind_List = '['
Kind_Null = '-'
Kind_Link = '/'
Kind_Union = 'u'
Kind_Obj = 'o'
Kind_Enum = 'e'
)
package typed package typed
import ( import (
"fmt"
"path"
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/typed/system"
) )
type Node struct { // typed.Node is a superset of the ipld.Node interface, and has additional behaviors.
// FUTURE: proxies most methods, plus adds just-in-time type checking on reads. //
// You can use a Validate call to force checking of the entire tree. // A typed.Node can be inspected for its typesystem.Type and typesystem.Kind,
// "Advanced Layouts" (e.g. HAMTs, etc) can be seamlessly presented as a plain map through this interface. // which conveys much more and richer information than the Data Model layer
} // ipld.ReprKind.
//
type MutableNode struct { // There are many different implementations of typed.Node.
// FUTURE: another impl of ipld.MutableNode we can return which checks all things at change time. // One implementation can wrap any other existing ipld.Node (i.e., it's zero-copy)
// This can proxy to some other implementation type that does real storage. // and promises that it has *already* been validated to match the typesystem.Type;
} // another implementation similarly wraps any other existing ipld.Node, but
// defers to the typesystem validation checking to fields that are accessed;
func Validate(ts Universe, t Type, node ipld.Node) []error { // and when using code generation tools, all of the generated native Golang
return validate(ts, t, node, "/") // types produced by the codegen will each individually implement typed.Node.
} type Node interface {
ipld.Node
// review: 'ts' param might not actually be necessary; everything relevant can be reached from t so far.
func validate(ts Universe, t Type, node ipld.Node, pth string) []error {
switch t2 := t.(type) {
case TypeBool:
if node.Kind() != 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.Kind())}
}
return nil
case TypeString:
if node.Kind() != ipld.ReprKind_String {
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.Kind())}
}
return nil
case TypeBytes:
if node.Kind() != ipld.ReprKind_Bytes {
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.Kind())}
}
return nil
case TypeInt:
if node.Kind() != ipld.ReprKind_Int {
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.Kind())}
}
return nil
case TypeFloat:
if node.Kind() != ipld.ReprKind_Float {
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.Kind())}
}
return nil
case TypeMap:
if node.Kind() != 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.Kind())}
}
keys, _ := node.Keys()
errs := []error(nil)
for _, k := range keys {
// FUTURE: if KeyType is an enum rather than string, do membership check.
child, _ := node.TraverseField(k)
if child.IsNull() {
if !t2.ValueNullable {
errs = append(errs, fmt.Errorf("Schema match failed: map at path %q contains unpermitted null in key %q", pth, k))
}
} else {
errs = append(errs, validate(ts, t2.ValueType, child, path.Join(pth, k))...)
}
}
return errs
case TypeList:
case TypeLink:
// TODO interesting case: would need resolver to keep checking.
case TypeUnion:
// TODO *several* interesting errors
case TypeObject:
switch t2.TupleStyle {
case false: // as map!
if node.Kind() != 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.Kind())}
}
// TODO loop over em
// TODO REVIEW order strictness questions?
case true: // as array!
} Type() typesystem.Type
case TypeEnum:
// TODO another interesting error
}
return nil
} }
package typed package typesystem
import ( import (
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
......
package typed package typesystem
import ( import (
"github.com/ipld/go-ipld-prime" ipld "github.com/ipld/go-ipld-prime"
) )
// FIXME make everything exported in this file a read-only accessor.
// TODO make most references to `Type` into `*Type`.
// TODO rename `TypeObject` into `TypeStruct`.
type TypeName string type TypeName string
type Type interface { type Type interface {
......
package typesystem
// FIXME make this a structure that exposes immutable views
type Universe map[TypeName]Type
package typesystem
import (
"fmt"
"path"
"github.com/ipld/go-ipld-prime"
)
func Validate(ts Universe, t Type, node ipld.Node) []error {
return validate(ts, t, node, "/")
}
// review: 'ts' param might not actually be necessary; everything relevant can be reached from t so far.
func validate(ts Universe, t Type, node ipld.Node, pth string) []error {
switch t2 := t.(type) {
case TypeBool:
if node.Kind() != 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.Kind())}
}
return nil
case TypeString:
if node.Kind() != ipld.ReprKind_String {
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.Kind())}
}
return nil
case TypeBytes:
if node.Kind() != ipld.ReprKind_Bytes {
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.Kind())}
}
return nil
case TypeInt:
if node.Kind() != ipld.ReprKind_Int {
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.Kind())}
}
return nil
case TypeFloat:
if node.Kind() != ipld.ReprKind_Float {
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.Kind())}
}
return nil
case TypeMap:
if node.Kind() != 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.Kind())}
}
keys, _ := node.Keys()
errs := []error(nil)
for _, k := range keys {
// FUTURE: if KeyType is an enum rather than string, do membership check.
child, _ := node.TraverseField(k)
if child.IsNull() {
if !t2.ValueNullable {
errs = append(errs, fmt.Errorf("Schema match failed: map at path %q contains unpermitted null in key %q", pth, k))
}
} else {
errs = append(errs, validate(ts, t2.ValueType, child, path.Join(pth, k))...)
}
}
return errs
case TypeList:
case TypeLink:
// TODO interesting case: would need resolver to keep checking.
case TypeUnion:
// TODO *several* interesting errors
case TypeObject:
switch t2.TupleStyle {
case false: // as map!
if node.Kind() != 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.Kind())}
}
// TODO loop over em
// TODO REVIEW order strictness questions?
case true: // as array!
}
case TypeEnum:
// TODO another interesting error
}
return nil
}
package typed package typesystem
import ( import (
"testing" "testing"
......
package typed
type Universe map[TypeName]Type
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