ifvrb==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))
}elseifvrb!=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{
// 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.
errs=append(errs,fmt.Errorf("%s is not a valid member: has representation kind %s",v,vt.RepresentationBehavior()))
case*TypeMap:
ifvt.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:
ifvt.RepresentationBehavior()!=ipld.Kind_Map{
errs=append(errs,fmt.Errorf("%s is not a valid member: has representation kind %s",v,vt.RepresentationBehavior()))
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()))
// 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.
// 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()
caseschemadmt.TypeList:
mkind=TypeList{dmt:t3}.RepresentationBehavior()
gotokindcheck
caseschemadmt.TypeLink:
mkind=TypeLink{dmt:t3}.RepresentationBehavior()
gotokindcheck
caseschemadmt.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.
caseschemadmt.TypeStruct:
// Check representation strategy first. Still has to be mappy.
t4:=TypeStruct{dmt:t3}
ift4.RepresentationBehavior()!=ipld.Kind_Map{
gotokindcheck// 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.
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.
caseschemadmt.TypeEnum:
mkind=TypeEnum{dmt:t3}.RepresentationBehavior()
gotokindcheck
caseschemadmt.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:
ifmkind!=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))
}
}
}
caseschemadmt.TypeEnum:
// Verify that:
// - each value in the enumeration has an entry in its representation table.