Commit ea79ea43 authored by Eric Myhre's avatar Eric Myhre

schema compiler: union validation rules.

Also, shift some error message text towards a more consistent phrasing.
parent 6897bb3c
......@@ -190,9 +190,9 @@ var rules = map[TypeKind][]rule{
for k, v := range r.discriminantTable {
vrb := ts.types[TypeReference(v)].RepresentationBehavior()
if vrb == ipld.Kind_Invalid { // this indicates a kinded union (the only thing that can't statically state its representation behavior), which deserves a special error message.
errs = append(errs, fmt.Errorf("kinded unions cannot be nested and member type %s is also a kinded union", v))
errs = append(errs, fmt.Errorf("%s is not a valid member: kinded unions cannot be nested and %s is also a kinded union", v, v))
} else if vrb != k {
errs = append(errs, fmt.Errorf("kind %s is declared to be received as type %s, but that type's representation kind is %s", k, v, vrb))
errs = append(errs, fmt.Errorf("kind mismatch: %s is declared to be received as type %s, but that type's representation's kind is %s", k, v, vrb))
}
}
return
......@@ -212,8 +212,31 @@ var rules = map[TypeKind][]rule{
func(t Type) bool { _, ok := t.(*TypeUnion).rstrat.(UnionRepresentation_Inline); return ok },
func(ts *TypeSystem, t Type) (errs []error) {
r := t.(*TypeUnion).rstrat.(UnionRepresentation_Inline)
for k, v := range r.discriminantTable {
// TODO: port the UnionRepresentation_Inline rules
// This is one of the more complicated rules.
// - many typekinds can be rejected as members outright, because they don't have any map representations.
// - maps themselves are acceptable, if they still have a map representation (although this is a janky thing to do in a protocol design, because it means there's at least one key that's now illeagal in that map, and we can't help you with that).
// - structs are acceptable if they have map representation... but we also validate in advance that the discriminant key doesn't collide with any field names in any of the structs.
// - other unions aren't ever valid members, even if their representation has a map kind, because the logical rules don't fit together. So we give distinct error messages for this.
for _, v := range r.discriminantTable {
switch vt := ts.types[TypeReference(v)].(type) {
case *TypeBool, *TypeString, *TypeBytes, *TypeInt, *TypeFloat, *TypeList, *TypeLink, *TypeEnum:
errs = append(errs, fmt.Errorf("%s is not a valid member: has representation kind %s", v, vt.RepresentationBehavior()))
case *TypeMap:
if vt.RepresentationBehavior() != ipld.Kind_Map {
errs = append(errs, fmt.Errorf("%s is not a valid member: has representation kind %s", v, vt.RepresentationBehavior()))
}
case *TypeUnion:
errs = append(errs, fmt.Errorf("%s is not a valid member: inline unions cannot directly contain other unions, because their representation rules would conflict", v))
case *TypeStruct:
if vt.RepresentationBehavior() != ipld.Kind_Map {
errs = append(errs, fmt.Errorf("%s is not a valid member: has representation kind %s", v, vt.RepresentationBehavior()))
}
for _, f := range vt.Fields() {
if r.DiscriminantKey() == vt.Representation().(StructRepresentation_Map).GetFieldKey(f) {
errs = append(errs, fmt.Errorf("%s is not a valid member: key collision: %s has a field that collides with %s's discriminant key when represented", v, v, t.Name()))
}
}
}
}
return
},
......
......@@ -37,95 +37,6 @@ func BuildTypeSystem(schdmt schemadmt.Schema) (*TypeSystem, []error) {
for itr := typesdmt.Iterator(); !itr.Done(); {
tn, t := itr.Next()
switch t2 := t.AsInterface().(type) {
case schemadmt.TypeUnion:
// Verify... oh boy. A lot of things; and each representation strategy has different constraints:
// - for everyone: that all referenced member types exist.
// - for everyone (but in distinctive ways): that each member type has a discriminant!
// - for keyed unions: that's sufficient (discriminant uniqueness already enforced by map).
// - for kinded unions: validate that that the stated kind actually matches what each type's representation kind is.
// - surprisingly, unions can nest... as long as they're not kinded. (In theory, kinded union nesting could be defined, as long as their discriminants are non-overlapping, but... why would you want this?)
// - for envelope unions: quick sanity check that discriminantKey and contentKey are distinct.
// - for inline unions: validate that all members have map kinds...
// - and more specifically are map or struct types (other unions are not allowed because they wouldn't fit together validly anyway)
// - and for structs, validate in advance that the discriminant key doesn't collide with any field names in any of the structs.
// - for stringprefix unions: that's sufficient (discriminant uniqueness already enforced by map).
// - for byteprefix unions: ... we'll come back to this later.
// Do the per-representation-strategy checks.
// Every representation strategy a check that there's a discriminant for every member (though they require slightly different setup).
// Some representation strategies also include quite a few more checks.
switch r := t2.FieldRepresentation().AsInterface().(type) {
case schemadmt.UnionRepresentation_Inline:
checkUnionDiscriminantInfo(tn, members, r.FieldDiscriminantTable(), &ee)
for itr := r.FieldDiscriminantTable().Iterator(); !itr.Done(); {
_, v := itr.Next()
// As with the switch above which handles kinded union members, we go for the full destructuring here.
// It's slightly overkill considering that most of the type kinds will flatly error in practice, but consistency is nice.
var mkind ipld.Kind
switch t3 := typesdmt.Lookup(v).AsInterface().(type) {
case schemadmt.TypeBool:
mkind = TypeBool{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeString:
mkind = TypeString{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeBytes:
mkind = TypeBytes{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeInt:
mkind = TypeInt{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeFloat:
mkind = TypeFloat{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeMap:
// For maps, we check the representation strategy -- it still has to be mappy! -- but that's it.
// Unlike for structs, where we can check ahead of time for field names which would collide with the discriminant key, with maps we're just stuck with that being a problem we can only discover at runtime.
mkind = TypeMap{dmt: t3}.RepresentationBehavior()
case schemadmt.TypeList:
mkind = TypeList{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeLink:
mkind = TypeLink{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeUnion:
ee = append(ee, fmt.Errorf("union %s has representation strategy inline, which can't sensibly compose with any other union strategy, so %s (which is another union type) is not a valid member", tn, v))
continue // kindcheck doesn't actually matter in this case; the error here isn't conditional on that.
case schemadmt.TypeStruct:
// Check representation strategy first. Still has to be mappy.
t4 := TypeStruct{dmt: t3}
if t4.RepresentationBehavior() != ipld.Kind_Map {
goto kindcheck // it'll fail, of course, but this goto DRY's the error message.
}
// Check for field name collisions.
// This uses the (temporarily) reified struct type info, so we can reuse that code which deals with rename directives.
switch r2 := t4.RepresentationStrategy().(type) {
case StructRepresentation_Map:
for _, f := range t4.Fields() {
if r.FieldDiscriminantKey().String() == r2.GetFieldKey(f) {
ee = append(ee, fmt.Errorf("union %s has representation strategy inline, and %s is not a valid member for it because it has a field that collides with discriminantKey when represented", tn, v))
}
}
default:
panic("unreachable") // We know that the none of the other struct representation strategies result in a map kind.
}
continue // kindcheck already done in a unique way in this case.
case schemadmt.TypeEnum:
mkind = TypeEnum{dmt: t3}.RepresentationBehavior()
goto kindcheck
case schemadmt.TypeCopy:
panic("no support for 'copy' types. I might want to reneg on whether these are even part of the schema dmt.")
default:
panic("unreachable")
}
kindcheck:
if mkind != ipld.Kind_Map {
ee = append(ee, fmt.Errorf("union %s has representation strategy inline, which requires all members have map representations, so %s (which has representation kind %s) is not a valid member", tn, v, mkind))
}
}
}
case schemadmt.TypeEnum:
// Verify that:
// - each value in the enumeration has an entry in its representation table.
......
......@@ -119,3 +119,7 @@ func (r UnionRepresentation_Kinded) GetMember(k ipld.Kind) Type {
}
return nil
}
func (r UnionRepresentation_Inline) DiscriminantKey() string {
return r.discriminantKey
}
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