Commit c9cf2100 authored by Eric Myhre's avatar Eric Myhre

Each 'Type*' now has read-only methods.

...and all fields are correspondingly unexported.

We're going full immutability on almost all this stuff.  I simply can't
imagine handling and maintaining this any other way.

The typesystem.Type interface also got a few more standard methods.
I'm not sure if having a Universe backpointer is going to be useful
very often or not, but it seems reasonable enough to do; we'll
definitely *have* that information during consistency checking and
reification time, so it should turn out easy to keep it.

The validate_test.go code is now looking a bit janky.  We need
initializers for the typesystem.Type* values.  Those will be the
typedeclaration.* package... which we'll write next.

Note that I skipped any accessor methods on `Type*` objects which are
for representation details.  These might get filled in later, but I'm
gonna see how far I can get without them.  (So far, there's some
uses of the representation details in the validate method, but we've
located that in the typesystem package anyway, so that's... fine.)
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent c8e3e1d4
......@@ -4,6 +4,12 @@ import (
"github.com/ipld/go-ipld-prime"
)
/* cookie-cutter standard interface stuff */
func (anyType) _Type() {}
func (t anyType) Universe() *Universe { return t.universe }
func (t anyType) Name() TypeName { return t.name }
func (TypeBool) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Bool
}
......@@ -28,16 +34,15 @@ func (TypeList) ReprKind() ipld.ReprKind {
func (TypeLink) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Link
}
func (tv TypeUnion) ReprKind() ipld.ReprKind {
// REVIEW: this may fib; has the bizarre property of being dependent on the *concrete value* for kinded unions!
if tv.Style == UnionStyle_Kinded {
func (t TypeUnion) ReprKind() ipld.ReprKind {
if t.style == UnionStyle_Kinded {
return ipld.ReprKind_Invalid
} else {
return ipld.ReprKind_Map
}
}
func (tv TypeObject) ReprKind() ipld.ReprKind {
if tv.TupleStyle {
func (t TypeObject) ReprKind() ipld.ReprKind {
if t.tupleStyle {
return ipld.ReprKind_List
} else {
return ipld.ReprKind_Map
......@@ -46,3 +51,103 @@ func (tv TypeObject) ReprKind() ipld.ReprKind {
func (TypeEnum) ReprKind() ipld.ReprKind {
return ipld.ReprKind_String
}
/* interesting methods per Type type */
// IsAnonymous is returns true if the type was unnamed. Unnamed types will
// claim to have a Name property like `{Foo:Bar}`, and this is not guaranteed
// to be a unique string for all types in the universe.
func (t TypeMap) IsAnonymous() bool {
return t.anonymous
}
// KeyType returns the Type of the map keys.
//
// Note that map keys will must always be some type which is representable as a
// string in the IPLD Data Model (e.g. either TypeString or TypeEnum).
func (t TypeMap) KeyType() Type {
return t.keyType
}
// ValueType returns to the Type of the map values.
func (t TypeMap) ValueType() Type {
return t.valueType
}
// ValueIsNullable returns a bool describing if the map values are permitted
// to be null.
func (t TypeMap) ValueIsNullable() bool {
return t.valueNullable
}
// IsAnonymous is returns true if the type was unnamed. Unnamed types will
// claim to have a Name property like `[Foo]`, and this is not guaranteed
// to be a unique string for all types in the universe.
func (t TypeList) IsAnonymous() bool {
return t.anonymous
}
// ValueType returns to the Type of the list values.
func (t TypeList) ValueType() Type {
return t.valueType
}
// ValueIsNullable returns a bool describing if the list values are permitted
// to be null.
func (t TypeList) ValueIsNullable() bool {
return t.valueNullable
}
// UnionMembers returns a set of all the types that can inhabit this Union.
func (t TypeUnion) UnionMembers() map[TypeName]Type {
m := make(map[TypeName]Type, len(t.values))
for k, v := range t.values {
m[k] = v
}
return m
}
// Fields returns a slice of descriptions of the object's fields.
func (t TypeObject) Fields() []ObjectField {
a := make([]ObjectField, len(t.fields))
for i := range t.fields {
a[i] = t.fields[i]
}
return a
}
// Name returns the string name of this field. The name is the string that
// will be used as a map key if the structure this field is a member of is
// serialized as a map representation.
func (f ObjectField) Name() string { return f.name }
// Type returns the Type of this field's value. Note the field may
// also be unset if it is either Optional or Nullable.
func (f ObjectField) Type() Type { return f.typ }
// IsOptional returns true if the field is allowed to be absent from the object.
// If IsOptional is false, the field may be absent from the serial representation
// of the object entirely.
//
// Note being optional is different than saying the value is permitted to be null!
// A field may be both nullable and optional simultaneously, or either, or neither.
func (f ObjectField) IsOptional() bool { return f.optional }
// IsNullable returns true if the field value is allowed to be null.
//
// If is Nullable is false, note that it's still possible that the field value
// will be absent if the field is Optional! Being nullable is unrelated to
// whether the field's presence is optional as a whole.
//
// Note that a field may be both nullable and optional simultaneously,
// or either, or neither.
func (f ObjectField) IsNullable() bool { return f.nullable }
// Members returns a slice the strings which are valid inhabitants of this enum.
func (t TypeEnum) Members() []string {
a := make([]string, len(t.members))
for i := range t.members {
a[i] = t.members[i]
}
return a
}
......@@ -4,16 +4,61 @@ import (
ipld "github.com/ipld/go-ipld-prime"
)
// FIXME make everything exported in this file a read-only accessor.
type TypeName string
// TODO make most references to `Type` into `*Type`.
// typesystem.Type is an union interface; each of the `Type*` concrete types
// in this package are one of its members.
//
// Specifically,
//
// TypeBool
// TypeString
// TypeBytes
// TypeInt
// TypeFloat
// TypeMap
// TypeList
// TypeLink
// TypeUnion
// TypeObject
// TypeEnum
//
// are all of the kinds of Type.
//
// This is a closed union; you can switch upon the above members without
// including a default case. The membership is closed by the unexported
// '_Type' method; you may use the BurntSushi/go-sumtype tool to check
// your switches for completeness.
//
// Many interesting properties of each Type are only defined for that specific
// type, so it's typical to use a type switch to handle each type of Type.
// (Your humble author is truly sorry for the word-mash that results from
// attempting to describe the types that describe the typesystem.Type.)
//
// For example, to inspect the kind of fields in a struct: you might
// cast a `Type` interface into `TypeStruct`, and then the `Fields()` on
// that `TypeStruct` can be inspected. (`Fields()` isn't defined for any
// other kind of Type.)
type Type interface {
// Unexported marker method to force the union closed.
_Type()
// TODO rename `TypeObject` into `TypeStruct`.
// Returns a pointer to the typesystem.Universe this type is a member of.
Universe() *Universe
type TypeName string
// Returns the string name of the Type. This name is unique within the
// universe this type is a member of, *unless* this type is Anonymous,
// in which case a string describing the type will still be returned, but
// that string will not be required to be unique.
Name() TypeName
type Type interface {
// Name() TypeName // annoying name collision.
// Returns the Representation Kind in the IPLD Data Model that this type
// is expected to be serialized as.
//
// Note that in one case, this will return `ipld.ReprKind_Invalid` --
// TypeUnion with Style=Kinded may be serialized as different kinds
// depending on their value, so we can't say from the type definition
// alone what kind we expect.
ReprKind() ipld.ReprKind
}
......@@ -31,47 +76,60 @@ var (
_ Type = TypeEnum{}
)
type anyType struct {
name TypeName
universe *Universe
}
type TypeBool struct {
Name TypeName
anyType
}
type TypeString struct {
Name TypeName
anyType
}
type TypeBytes struct {
Name TypeName
anyType
}
type TypeInt struct {
Name TypeName
anyType
}
type TypeFloat struct {
Name TypeName
anyType
}
type TypeMap struct {
Name TypeName
Anon bool
KeyType Type // must be ReprKind==string (e.g. Type==String|Enum).
ValueType Type
ValueNullable bool
anyType
anonymous bool
keyType Type // must be ReprKind==string (e.g. Type==String|Enum).
valueType Type
valueNullable bool
}
type TypeList struct {
Name TypeName
Anon bool
ValueType Type
ValueNullable bool
anyType
anonymous bool
valueType Type
valueNullable bool
}
type TypeLink struct {
Name TypeName
anyType
// ...?
}
type TypeUnion struct {
Name TypeName
Style UnionStyle
ValuesKinded map[ipld.ReprKind]Type // for Style==Kinded
Values map[TypeName]Type // for Style!=Kinded
TypeHintKey string // for Style==Envelope|Inline
ContentKey string // for Style==Envelope
anyType
style UnionStyle
valuesKinded map[ipld.ReprKind]Type // for Style==Kinded
values map[TypeName]Type // for Style!=Kinded (always defined, just not used in Kinded lookups)
typeHintKey string // for Style==Envelope|Inline
contentKey string // for Style==Envelope
}
type UnionStyle struct{ x string }
var (
......@@ -82,18 +140,18 @@ var (
)
type TypeObject struct {
Name TypeName
TupleStyle bool // if true, ReprKind=Array instead of map (and optional fields are invalid!)
Fields []ObjectField
anyType
tupleStyle bool // if true, ReprKind=Array instead of map (and optional fields are invalid!)
fields []ObjectField
}
type ObjectField struct {
Name string
Type Type
Optional bool
Nullable bool
name string
typ Type
optional bool
nullable bool
}
type TypeEnum struct {
Name string
Values []string
anyType
members []string
}
......@@ -16,32 +16,32 @@ 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 []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 []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 []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 []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 []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())}
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)
......@@ -49,11 +49,11 @@ func validate(ts Universe, t Type, node ipld.Node, pth string) []error {
// FUTURE: if KeyType is an enum rather than string, do membership check.
child, _ := node.TraverseField(k)
if child.IsNull() {
if !t2.ValueNullable {
if !t2.ValueIsNullable() {
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))...)
errs = append(errs, validate(ts, t2.ValueType(), child, path.Join(pth, k))...)
}
}
return errs
......@@ -63,10 +63,10 @@ func validate(ts Universe, t Type, node ipld.Node, pth string) []error {
case TypeUnion:
// TODO *several* interesting errors
case TypeObject:
switch t2.TupleStyle {
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())}
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?
......
......@@ -13,7 +13,7 @@ func TestSimpleTypes(t *testing.T) {
n1 := &ipldfree.Node{}
n1.SetString("asdf")
t1 := TypeString{
Name: "Foo",
anyType{name: "Foo"},
}
Wish(t,
Validate(nil, t1, n1),
......
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