Integrate BACKBEAT SDK and resolve KACHING license validation

Major integrations and fixes:
- Added BACKBEAT SDK integration for P2P operation timing
- Implemented beat-aware status tracking for distributed operations
- Added Docker secrets support for secure license management
- Resolved KACHING license validation via HTTPS/TLS
- Updated docker-compose configuration for clean stack deployment
- Disabled rollback policies to prevent deployment failures
- Added license credential storage (CHORUS-DEV-MULTI-001)

Technical improvements:
- BACKBEAT P2P operation tracking with phase management
- Enhanced configuration system with file-based secrets
- Improved error handling for license validation
- Clean separation of KACHING and CHORUS deployment stacks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-09-06 07:56:26 +10:00
parent 543ab216f9
commit 9bdcbe0447
4730 changed files with 1480093 additions and 1916 deletions

View File

@@ -0,0 +1,427 @@
package schemadmt
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/schema"
)
// Compile transforms a description of a schema in raw data model ("dmt") form
// into a compiled schema.TypeSystem, which is the ready-to-use form.
//
// The first parameter is mutated by this process,
// and the second parameter is the data source.
//
// The compilation process includes first inserting the "prelude" types into the
// schema.TypeSystem -- that is, the "type Bool bool" and "type String string", etc,
// which are generally presumed to be present in any type system.
//
// The compilation process attempts to check the validity of the schema at a logical level as it goes.
// For example, references to type names not present elsewhere in the same schema are now an error
// (even though that has been easily representable in the dmt.Schema form up until this point).
//
// Note that this API is EXPERIMENTAL and will likely change.
// It supports many features of IPLD Schemas,
// but it may yet not support all of them.
// It supports several validations for logical coherency of schemas,
// but may not yet successfully reject all invalid schemas.
func Compile(ts *schema.TypeSystem, node *Schema) error {
// Prelude; probably belongs elsewhere.
{
ts.Accumulate(schema.SpawnBool("Bool"))
ts.Accumulate(schema.SpawnInt("Int"))
ts.Accumulate(schema.SpawnFloat("Float"))
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnBytes("Bytes"))
ts.Accumulate(schema.SpawnAny("Any"))
ts.Accumulate(schema.SpawnMap("Map", "String", "Any", false))
ts.Accumulate(schema.SpawnList("List", "Any", false))
// Should be &Any, really.
ts.Accumulate(schema.SpawnLink("Link"))
// TODO: schema package lacks support?
// ts.Accumulate(schema.SpawnUnit("Null", NullRepr))
}
for _, name := range node.Types.Keys {
defn := node.Types.Values[name]
// TODO: once ./schema supports anonymous/inline types, remove the ts argument.
typ, err := spawnType(ts, name, defn)
if err != nil {
return err
}
ts.Accumulate(typ)
}
// TODO: if this fails and the user forgot to check Compile's returned error,
// we can leave the TypeSystem in an unfortunate broken state:
// they can obtain types out of the TypeSystem and they are non-nil,
// but trying to use them in any way may result in panics.
// Consider making that less prone to misuse, such as making it illegal to
// call TypeByName until ValidateGraph is happy.
if errs := ts.ValidateGraph(); errs != nil {
// Return the first error.
for _, err := range errs {
return err
}
}
return nil
}
// Note that the parser and compiler support defaults. We're lacking support in bindnode.
func todoFromImplicitlyFalseBool(b *bool) bool {
if b == nil {
return false
}
return *b
}
func anonTypeName(nameOrDefn TypeNameOrInlineDefn) string {
if nameOrDefn.TypeName != nil {
return *nameOrDefn.TypeName
}
defn := *nameOrDefn.InlineDefn
switch {
case defn.TypeDefnMap != nil:
defn := defn.TypeDefnMap
return fmt.Sprintf("Map__%s__%s", defn.KeyType, anonTypeName(defn.ValueType))
case defn.TypeDefnList != nil:
defn := defn.TypeDefnList
return fmt.Sprintf("List__%s", anonTypeName(defn.ValueType))
case defn.TypeDefnLink != nil:
return anonLinkName(*defn.TypeDefnLink)
default:
panic(fmt.Errorf("%#v", defn))
}
}
func anonLinkName(defn TypeDefnLink) string {
if defn.ExpectedType != nil {
return fmt.Sprintf("Link__%s", *defn.ExpectedType)
}
return "Link__Link"
}
func parseKind(s string) datamodel.Kind {
switch s {
case "map":
return datamodel.Kind_Map
case "list":
return datamodel.Kind_List
case "null":
return datamodel.Kind_Null
case "bool":
return datamodel.Kind_Bool
case "int":
return datamodel.Kind_Int
case "float":
return datamodel.Kind_Float
case "string":
return datamodel.Kind_String
case "bytes":
return datamodel.Kind_Bytes
case "link":
return datamodel.Kind_Link
default:
return datamodel.Kind_Invalid
}
}
func spawnType(ts *schema.TypeSystem, name schema.TypeName, defn TypeDefn) (schema.Type, error) {
switch {
// Scalar types without parameters.
case defn.TypeDefnBool != nil:
return schema.SpawnBool(name), nil
case defn.TypeDefnString != nil:
return schema.SpawnString(name), nil
case defn.TypeDefnBytes != nil:
return schema.SpawnBytes(name), nil
case defn.TypeDefnInt != nil:
return schema.SpawnInt(name), nil
case defn.TypeDefnFloat != nil:
return schema.SpawnFloat(name), nil
case defn.TypeDefnList != nil:
typ := defn.TypeDefnList
tname := ""
if typ.ValueType.TypeName != nil {
tname = *typ.ValueType.TypeName
} else if tname = anonTypeName(typ.ValueType); ts.TypeByName(tname) == nil {
anonDefn := TypeDefn{
TypeDefnMap: typ.ValueType.InlineDefn.TypeDefnMap,
TypeDefnList: typ.ValueType.InlineDefn.TypeDefnList,
TypeDefnLink: typ.ValueType.InlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
switch {
case typ.Representation == nil ||
typ.Representation.ListRepresentation_List != nil:
// default behavior
default:
return nil, fmt.Errorf("TODO: support other list repr in schema package")
}
return schema.SpawnList(name,
tname,
todoFromImplicitlyFalseBool(typ.ValueNullable),
), nil
case defn.TypeDefnMap != nil:
typ := defn.TypeDefnMap
tname := ""
if typ.ValueType.TypeName != nil {
tname = *typ.ValueType.TypeName
} else if tname = anonTypeName(typ.ValueType); ts.TypeByName(tname) == nil {
anonDefn := TypeDefn{
TypeDefnMap: typ.ValueType.InlineDefn.TypeDefnMap,
TypeDefnList: typ.ValueType.InlineDefn.TypeDefnList,
TypeDefnLink: typ.ValueType.InlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
switch {
case typ.Representation == nil ||
typ.Representation.MapRepresentation_Map != nil:
// default behavior
case typ.Representation.MapRepresentation_Stringpairs != nil:
return nil, fmt.Errorf("TODO: support stringpairs map repr in schema package")
default:
return nil, fmt.Errorf("TODO: support other map repr in schema package")
}
return schema.SpawnMap(name,
typ.KeyType,
tname,
todoFromImplicitlyFalseBool(typ.ValueNullable),
), nil
case defn.TypeDefnStruct != nil:
typ := defn.TypeDefnStruct
var fields []schema.StructField
for _, fname := range typ.Fields.Keys {
field := typ.Fields.Values[fname]
tname := ""
if field.Type.TypeName != nil {
tname = *field.Type.TypeName
} else if tname = anonTypeName(field.Type); ts.TypeByName(tname) == nil {
// Note that TypeDefn and InlineDefn aren't the same enum.
anonDefn := TypeDefn{
TypeDefnMap: field.Type.InlineDefn.TypeDefnMap,
TypeDefnList: field.Type.InlineDefn.TypeDefnList,
TypeDefnLink: field.Type.InlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
fields = append(fields, schema.SpawnStructField(fname,
tname,
todoFromImplicitlyFalseBool(field.Optional),
todoFromImplicitlyFalseBool(field.Nullable),
))
}
var repr schema.StructRepresentation
switch {
case typ.Representation.StructRepresentation_Map != nil:
rp := typ.Representation.StructRepresentation_Map
if rp.Fields == nil {
repr = schema.SpawnStructRepresentationMap2(nil, nil)
break
}
renames := make(map[string]string, len(rp.Fields.Keys))
implicits := make(map[string]schema.ImplicitValue, len(rp.Fields.Keys))
for _, name := range rp.Fields.Keys {
details := rp.Fields.Values[name]
if details.Rename != nil {
renames[name] = *details.Rename
}
if imp := details.Implicit; imp != nil {
var sumVal schema.ImplicitValue
switch {
case imp.Bool != nil:
sumVal = schema.ImplicitValue_Bool(*imp.Bool)
case imp.String != nil:
sumVal = schema.ImplicitValue_String(*imp.String)
case imp.Int != nil:
sumVal = schema.ImplicitValue_Int(*imp.Int)
default:
panic("TODO: implicit value kind")
}
implicits[name] = sumVal
}
}
repr = schema.SpawnStructRepresentationMap2(renames, implicits)
case typ.Representation.StructRepresentation_Tuple != nil:
rp := typ.Representation.StructRepresentation_Tuple
if rp.FieldOrder == nil {
repr = schema.SpawnStructRepresentationTuple()
break
}
return nil, fmt.Errorf("TODO: support for tuples with field orders in the schema package")
case typ.Representation.StructRepresentation_Stringjoin != nil:
join := typ.Representation.StructRepresentation_Stringjoin.Join
if join == "" {
return nil, fmt.Errorf("stringjoin has empty join value")
}
repr = schema.SpawnStructRepresentationStringjoin(join)
default:
return nil, fmt.Errorf("TODO: support other struct repr in schema package")
}
return schema.SpawnStruct(name,
fields,
repr,
), nil
case defn.TypeDefnUnion != nil:
typ := defn.TypeDefnUnion
var members []schema.TypeName
for _, member := range typ.Members {
if member.TypeName != nil {
members = append(members, *member.TypeName)
} else {
tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink)
members = append(members, tname)
if ts.TypeByName(tname) == nil {
anonDefn := TypeDefn{
TypeDefnLink: member.UnionMemberInlineDefn.TypeDefnLink,
}
anonType, err := spawnType(ts, tname, anonDefn)
if err != nil {
return nil, err
}
ts.Accumulate(anonType)
}
}
}
remainingMembers := make(map[string]bool)
for _, memberName := range members {
remainingMembers[memberName] = true
}
validMember := func(memberName string) error {
switch remaining, known := remainingMembers[memberName]; {
case remaining:
remainingMembers[memberName] = false
return nil
case !known:
return fmt.Errorf("%q is not a valid member of union %q", memberName, name)
default:
return fmt.Errorf("%q is duplicate in the union repr of %q", memberName, name)
}
}
var repr schema.UnionRepresentation
switch {
case typ.Representation.UnionRepresentation_Kinded != nil:
rp := typ.Representation.UnionRepresentation_Kinded
table := make(map[datamodel.Kind]schema.TypeName, len(rp.Keys))
for _, kindStr := range rp.Keys {
kind := parseKind(kindStr)
member := rp.Values[kindStr]
switch {
case member.TypeName != nil:
memberName := *member.TypeName
validMember(memberName)
table[kind] = memberName
case member.UnionMemberInlineDefn != nil:
tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink)
validMember(tname)
table[kind] = tname
}
}
repr = schema.SpawnUnionRepresentationKinded(table)
case typ.Representation.UnionRepresentation_Keyed != nil:
rp := typ.Representation.UnionRepresentation_Keyed
table := make(map[string]schema.TypeName, len(rp.Keys))
for _, key := range rp.Keys {
member := rp.Values[key]
switch {
case member.TypeName != nil:
memberName := *member.TypeName
validMember(memberName)
table[key] = memberName
case member.UnionMemberInlineDefn != nil:
tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink)
validMember(tname)
table[key] = tname
}
}
repr = schema.SpawnUnionRepresentationKeyed(table)
case typ.Representation.UnionRepresentation_StringPrefix != nil:
prefixes := typ.Representation.UnionRepresentation_StringPrefix.Prefixes
for _, key := range prefixes.Keys {
validMember(prefixes.Values[key])
}
repr = schema.SpawnUnionRepresentationStringprefix("", prefixes.Values)
default:
return nil, fmt.Errorf("TODO: support other union repr in schema package")
}
for memberName, remaining := range remainingMembers {
if remaining {
return nil, fmt.Errorf("%q is not present in the union repr of %q", memberName, name)
}
}
return schema.SpawnUnion(name,
members,
repr,
), nil
case defn.TypeDefnEnum != nil:
typ := defn.TypeDefnEnum
var repr schema.EnumRepresentation
// TODO: we should probably also reject duplicates.
validMember := func(name string) bool {
for _, memberName := range typ.Members {
if memberName == name {
return true
}
}
return false
}
switch {
case typ.Representation.EnumRepresentation_String != nil:
rp := typ.Representation.EnumRepresentation_String
for memberName := range rp.Values {
if !validMember(memberName) {
return nil, fmt.Errorf("%q is not a valid member of enum %q", memberName, name)
}
}
repr = schema.EnumRepresentation_String(rp.Values)
case typ.Representation.EnumRepresentation_Int != nil:
rp := typ.Representation.EnumRepresentation_Int
for memberName := range rp.Values {
if !validMember(memberName) {
return nil, fmt.Errorf("%q is not a valid member of enum %q", memberName, name)
}
}
repr = schema.EnumRepresentation_Int(rp.Values)
default:
return nil, fmt.Errorf("TODO: support other enum repr in schema package")
}
return schema.SpawnEnum(name,
typ.Members,
repr,
), nil
case defn.TypeDefnLink != nil:
typ := defn.TypeDefnLink
if typ.ExpectedType == nil {
return schema.SpawnLink(name), nil
}
return schema.SpawnLinkReference(name, *typ.ExpectedType), nil
case defn.TypeDefnAny != nil:
return schema.SpawnAny(name), nil
default:
panic(fmt.Errorf("%#v", defn))
}
}

30
vendor/github.com/ipld/go-ipld-prime/schema/dmt/doc.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/*
Package schema/dmt contains types and functions for dealing with the data model form of IPLD Schemas.
(DMT is short for "data model tree" -- see https://ipld.io/glossary/#dmt .)
As with anything that's IPLD data model, this data can be serialized or deserialized into a wide variety of codecs.
To contrast this package with some of its neighbors and with some various formats for the data this package describes:
Schemas also have a DSL (a domain-specific language -- something that's meant to look nice, and be easy for humans to read and write),
which are parsed by the `schema/dsl` package, and produce a DMT form (defined by and handled by this package).
Schemas also have a compiled form, which is the in-memory structure that this library uses when working with them;
this compiled form differs from the DMT because it can use pointers (and that includes cyclic pointers, which is something the DMT form cannot contain).
We use the DMT form (this package) to produce the compiled form (which is the `schema` package).
Creating a Compiled schema either flows from DSL(text)->`schema/dsl`->`schema/dmt`->`schema`,
or just (some codec, e.g. JSON or CBOR or etc)->`schema/dmt`->`schema`.
The `dmt.Schema` type describes the data found at the root of an IPLD Schema document.
The `Compile` function turns such data into a `schema.TypeSystem` that is ready to be used.
The `dmt.Prototype.Schema` value is a NodePrototype that can be used to handle IPLD Schemas in DMT form as regular IPLD Nodes.
Typically this package is imported aliased as "schemadmt",
since "dmt" is a fairly generic term in the IPLD ecosystem
(see https://ipld.io/glossary/#dmt ).
Many types in this package lack documentation directly on the type;
generally, these are structs that match the IPLD schema-schema,
and so you can find descriptions of them in documentation for the schema-schema.
*/
package schemadmt

View File

@@ -0,0 +1,27 @@
package schemadmt
import (
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode"
)
// ConcatenateSchemas returns a new schema DMT object containing the
// type declarations from both.
//
// As is usual for DMT form data, there is no check about the validity
// of the result yet; you'll need to apply `Compile` on the produced value
// to produce a usable compiled typesystem or to become certain that
// all references in the DMT are satisfied, etc.
func ConcatenateSchemas(a, b *Schema) *Schema {
// The joy of having an intermediate form that's just regular data model:
// we can implement this by simply using data model "copy" operations,
// and the result is correct.
nb := Prototypes.Schema.NewBuilder()
if err := datamodel.Copy(bindnode.Wrap(a, Prototypes.Schema.Type()), nb); err != nil {
panic(err)
}
if err := datamodel.Copy(bindnode.Wrap(b, Prototypes.Schema.Type()), nb); err != nil {
panic(err)
}
return bindnode.Unwrap(nb.Build()).(*Schema)
}

View File

@@ -0,0 +1,452 @@
package schemadmt
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema"
)
// Prototypes contains some schema.TypedPrototype values which match
// the IPLD schema-schema -- that is, the schema that describes IPLD schemas.
// These prototypes create an in-memory representation that is backed by
// structs in this package and bindnode.
var Prototypes struct {
Schema schema.TypedPrototype
}
//go:generate go run -tags=schemadmtgen gen.go
// TypeSystem is a compiled equivalent of the IPLD schema-schema -- that is, the schema that describes IPLD schemas.
//
// The IPLD schema-schema can be found at https://ipld.io/specs/schemas/schema-schema.ipldsch .
var TypeSystem schema.TypeSystem
// In this init function, we manually create a type system that *matches* the IPLD schema-schema.
// This manual work is unfortunate, and also must be kept in-sync manually,
// but is important because breaks a cyclic dependency --
// we use the compiled schema-schema produced by this to parse other schema documents.
// We would also use it to parse... the IPLD schema-schema... if that weren't a cyclic dependency.
func init() {
var ts schema.TypeSystem
ts.Init()
// I've elided all references to Advancedlayouts stuff for the moment.
// (Not because it's particularly hard or problematic; I just want to draw a slightly smaller circle first.)
// Prelude
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnBool("Bool"))
ts.Accumulate(schema.SpawnInt("Int"))
ts.Accumulate(schema.SpawnFloat("Float"))
ts.Accumulate(schema.SpawnBytes("Bytes"))
// Schema-schema!
// In the same order as the spec's ipldsch file.
// Note that ADL stuff is excluded for now, as per above.
ts.Accumulate(schema.SpawnString("TypeName"))
ts.Accumulate(schema.SpawnStruct("Schema",
[]schema.StructField{
schema.SpawnStructField("types", "Map__TypeName__TypeDefn", false, false),
// also: `advanced AdvancedDataLayoutMap`, but as commented above, we'll pursue this later.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__TypeName__TypeDefn",
"TypeName", "TypeDefn", false,
))
ts.Accumulate(schema.SpawnUnion("TypeDefn",
[]schema.TypeName{
"TypeDefnBool",
"TypeDefnString",
"TypeDefnBytes",
"TypeDefnInt",
"TypeDefnFloat",
"TypeDefnMap",
"TypeDefnList",
"TypeDefnLink",
"TypeDefnUnion",
"TypeDefnStruct",
"TypeDefnEnum",
"TypeDefnUnit",
"TypeDefnAny",
"TypeDefnCopy",
},
// TODO: spec uses inline repr.
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"bool": "TypeDefnBool",
"string": "TypeDefnString",
"bytes": "TypeDefnBytes",
"int": "TypeDefnInt",
"float": "TypeDefnFloat",
"map": "TypeDefnMap",
"list": "TypeDefnList",
"link": "TypeDefnLink",
"union": "TypeDefnUnion",
"struct": "TypeDefnStruct",
"enum": "TypeDefnEnum",
"unit": "TypeDefnUnit",
"any": "TypeDefnAny",
"copy": "TypeDefnCopy",
}),
))
ts.Accumulate(schema.SpawnUnion("TypeNameOrInlineDefn",
[]schema.TypeName{
"TypeName",
"InlineDefn",
},
schema.SpawnUnionRepresentationKinded(map[datamodel.Kind]schema.TypeName{
datamodel.Kind_String: "TypeName",
datamodel.Kind_Map: "InlineDefn",
}),
))
ts.Accumulate(schema.SpawnUnion("InlineDefn",
[]schema.TypeName{
"TypeDefnMap",
"TypeDefnList",
"TypeDefnLink",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"map": "TypeDefnMap",
"list": "TypeDefnList",
"link": "TypeDefnLink",
}),
))
ts.Accumulate(schema.SpawnStruct("TypeDefnBool",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnString",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnBytes",
[]schema.StructField{},
// No BytesRepresentation, since we omit ADL stuff.
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnInt",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnFloat",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnMap",
[]schema.StructField{
schema.SpawnStructField("keyType", "TypeName", false, false),
schema.SpawnStructField("valueType", "TypeNameOrInlineDefn", false, false),
schema.SpawnStructField("valueNullable", "Bool", true, false), // TODO: wants to use the "implicit" feature, but not supported yet
schema.SpawnStructField("representation", "MapRepresentation", true, false), // XXXXXX
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("MapRepresentation",
[]schema.TypeName{
"MapRepresentation_Map",
"MapRepresentation_Stringpairs",
"MapRepresentation_Listpairs",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"map": "MapRepresentation_Map",
"stringpairs": "MapRepresentation_Stringpairs",
"listpairs": "MapRepresentation_Listpairs",
}),
))
ts.Accumulate(schema.SpawnStruct("MapRepresentation_Map",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("MapRepresentation_Stringpairs",
[]schema.StructField{
schema.SpawnStructField("innerDelim", "String", false, false),
schema.SpawnStructField("entryDelim", "String", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("MapRepresentation_Listpairs",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnList",
[]schema.StructField{
schema.SpawnStructField("valueType", "TypeNameOrInlineDefn", false, false),
schema.SpawnStructField("valueNullable", "Bool", true, false), // TODO: wants to use the "implicit" feature, but not supported yet
schema.SpawnStructField("representation", "ListRepresentation", true, false), // XXXXXX
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("ListRepresentation",
[]schema.TypeName{
"ListRepresentation_List",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"list": "ListRepresentation_List",
}),
))
ts.Accumulate(schema.SpawnStruct("ListRepresentation_List",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnUnion",
[]schema.StructField{
// n.b. we could conceivably allow TypeNameOrInlineDefn here rather than just TypeName. but... we'd rather not: imagine what that means about the type-level behavior of the union: the name munge for the anonymous type would suddenly become load-bearing. would rather not.
schema.SpawnStructField("members", "List__UnionMember", false, false),
schema.SpawnStructField("representation", "UnionRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnList("List__UnionMember",
"UnionMember", false,
))
ts.Accumulate(schema.SpawnUnion("UnionMember",
[]schema.TypeName{
"TypeName",
"UnionMemberInlineDefn",
},
schema.SpawnUnionRepresentationKinded(map[datamodel.Kind]schema.TypeName{
datamodel.Kind_String: "TypeName",
datamodel.Kind_Map: "UnionMemberInlineDefn",
}),
))
ts.Accumulate(schema.SpawnUnion("UnionMemberInlineDefn",
[]schema.TypeName{
"TypeDefnLink",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"link": "TypeDefnLink",
}),
))
ts.Accumulate(schema.SpawnList("List__TypeName", // todo: this is a slight hack: should be an anon inside TypeDefnUnion.members.
"TypeName", false,
))
ts.Accumulate(schema.SpawnStruct("TypeDefnLink",
[]schema.StructField{
schema.SpawnStructField("expectedType", "TypeName", true, false), // todo: this uses an implicit with a value of 'any' in the schema-schema, but that's been questioned before. maybe it should simply be an optional.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("UnionRepresentation",
[]schema.TypeName{
"UnionRepresentation_Kinded",
"UnionRepresentation_Keyed",
"UnionRepresentation_Envelope",
"UnionRepresentation_Inline",
"UnionRepresentation_StringPrefix",
"UnionRepresentation_BytesPrefix",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"kinded": "UnionRepresentation_Kinded",
"keyed": "UnionRepresentation_Keyed",
"envelope": "UnionRepresentation_Envelope",
"inline": "UnionRepresentation_Inline",
"stringprefix": "UnionRepresentation_StringPrefix",
"byteprefix": "UnionRepresentation_BytesPrefix",
}),
))
ts.Accumulate(schema.SpawnMap("UnionRepresentation_Kinded",
"RepresentationKind", "UnionMember", false,
))
ts.Accumulate(schema.SpawnMap("UnionRepresentation_Keyed",
"String", "UnionMember", false,
))
ts.Accumulate(schema.SpawnMap("Map__String__UnionMember",
"TypeName", "TypeDefn", false,
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_Envelope",
[]schema.StructField{
schema.SpawnStructField("discriminantKey", "String", false, false),
schema.SpawnStructField("contentKey", "String", false, false),
schema.SpawnStructField("discriminantTable", "Map__String__UnionMember", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_Inline",
[]schema.StructField{
schema.SpawnStructField("discriminantKey", "String", false, false),
schema.SpawnStructField("discriminantTable", "Map__String__TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_StringPrefix",
[]schema.StructField{
schema.SpawnStructField("prefixes", "Map__String__TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("UnionRepresentation_BytesPrefix",
[]schema.StructField{
schema.SpawnStructField("prefixes", "Map__HexString__TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__HexString__TypeName",
"String", "TypeName", false,
))
ts.Accumulate(schema.SpawnString("HexString"))
ts.Accumulate(schema.SpawnMap("Map__String__TypeName",
"String", "TypeName", false,
))
ts.Accumulate(schema.SpawnMap("Map__TypeName__Int",
"String", "Int", false,
))
ts.Accumulate(schema.SpawnString("RepresentationKind")) // todo: RepresentationKind is supposed to be an enum, but we're puting it to a string atm.
ts.Accumulate(schema.SpawnStruct("TypeDefnStruct",
[]schema.StructField{
schema.SpawnStructField("fields", "Map__FieldName__StructField", false, false), // todo: dodging inline defn's again.
schema.SpawnStructField("representation", "StructRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__FieldName__StructField",
"FieldName", "StructField", false,
))
ts.Accumulate(schema.SpawnString("FieldName"))
ts.Accumulate(schema.SpawnStruct("StructField",
[]schema.StructField{
schema.SpawnStructField("type", "TypeNameOrInlineDefn", false, false),
schema.SpawnStructField("optional", "Bool", true, false), // todo: wants to use the "implicit" feature, but not supported yet
schema.SpawnStructField("nullable", "Bool", true, false), // todo: wants to use the "implicit" feature, but not supported yet
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("StructRepresentation",
[]schema.TypeName{
"StructRepresentation_Map",
"StructRepresentation_Tuple",
"StructRepresentation_Stringpairs",
"StructRepresentation_Stringjoin",
"StructRepresentation_Listpairs",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"map": "StructRepresentation_Map",
"tuple": "StructRepresentation_Tuple",
"stringpairs": "StructRepresentation_Stringpairs",
"stringjoin": "StructRepresentation_Stringjoin",
"listpairs": "StructRepresentation_Listpairs",
}),
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Map",
[]schema.StructField{
schema.SpawnStructField("fields", "Map__FieldName__StructRepresentation_Map_FieldDetails", true, false), // todo: dodging inline defn's again.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnMap("Map__FieldName__StructRepresentation_Map_FieldDetails",
"FieldName", "StructRepresentation_Map_FieldDetails", false,
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Map_FieldDetails",
[]schema.StructField{
schema.SpawnStructField("rename", "String", true, false),
schema.SpawnStructField("implicit", "AnyScalar", true, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Tuple",
[]schema.StructField{
schema.SpawnStructField("fieldOrder", "List__FieldName", true, false), // todo: dodging inline defn's again.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnList("List__FieldName",
"FieldName", false,
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Stringpairs",
[]schema.StructField{
schema.SpawnStructField("innerDelim", "String", false, false),
schema.SpawnStructField("entryDelim", "String", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Stringjoin",
[]schema.StructField{
schema.SpawnStructField("join", "String", false, false), // review: "delim" would seem more consistent with others -- but this is currently what the schema-schema says.
schema.SpawnStructField("fieldOrder", "List__FieldName", true, false), // todo: dodging inline defn's again.
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("StructRepresentation_Listpairs",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnEnum",
[]schema.StructField{
schema.SpawnStructField("members", "List__EnumMember", false, false),
schema.SpawnStructField("representation", "EnumRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("Unit", // todo: we should formalize the introdution of unit as first class type kind.
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnList("List__EnumMember",
"EnumMember", false,
))
ts.Accumulate(schema.SpawnString("EnumMember"))
ts.Accumulate(schema.SpawnUnion("EnumRepresentation",
[]schema.TypeName{
"EnumRepresentation_String",
"EnumRepresentation_Int",
},
schema.SpawnUnionRepresentationKeyed(map[string]schema.TypeName{
"string": "EnumRepresentation_String",
"int": "EnumRepresentation_Int",
}),
))
ts.Accumulate(schema.SpawnMap("EnumRepresentation_String",
"EnumMember", "String", false,
))
ts.Accumulate(schema.SpawnMap("EnumRepresentation_Int",
"EnumMember", "Int", false,
))
ts.Accumulate(schema.SpawnStruct("TypeDefnUnit",
[]schema.StructField{
schema.SpawnStructField("representation", "UnitRepresentation", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnString("UnitRepresentation")) // TODO: enum
ts.Accumulate(schema.SpawnStruct("TypeDefnAny",
[]schema.StructField{},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnStruct("TypeDefnCopy",
[]schema.StructField{
schema.SpawnStructField("fromType", "TypeName", false, false),
},
schema.StructRepresentation_Map{},
))
ts.Accumulate(schema.SpawnUnion("AnyScalar",
[]schema.TypeName{
"Bool",
"String",
"Bytes",
"Int",
"Float",
},
schema.SpawnUnionRepresentationKinded(map[datamodel.Kind]schema.TypeName{
datamodel.Kind_Bool: "Bool",
datamodel.Kind_String: "String",
datamodel.Kind_Bytes: "Bytes",
datamodel.Kind_Int: "Int",
datamodel.Kind_Float: "Float",
}),
))
if errs := ts.ValidateGraph(); errs != nil {
for _, err := range errs {
fmt.Printf("- %s\n", err)
}
panic("not happening")
}
TypeSystem = ts
Prototypes.Schema = bindnode.Prototype(
(*Schema)(nil),
TypeSystem.TypeByName("Schema"),
)
}

View File

@@ -0,0 +1,215 @@
package schemadmt
type Schema struct {
Types Map__TypeName__TypeDefn
}
type Map__TypeName__TypeDefn struct {
Keys []string
Values map[string]TypeDefn
}
type TypeDefn struct {
TypeDefnBool *TypeDefnBool
TypeDefnString *TypeDefnString
TypeDefnBytes *TypeDefnBytes
TypeDefnInt *TypeDefnInt
TypeDefnFloat *TypeDefnFloat
TypeDefnMap *TypeDefnMap
TypeDefnList *TypeDefnList
TypeDefnLink *TypeDefnLink
TypeDefnUnion *TypeDefnUnion
TypeDefnStruct *TypeDefnStruct
TypeDefnEnum *TypeDefnEnum
TypeDefnUnit *TypeDefnUnit
TypeDefnAny *TypeDefnAny
TypeDefnCopy *TypeDefnCopy
}
type TypeNameOrInlineDefn struct {
TypeName *string
InlineDefn *InlineDefn
}
type InlineDefn struct {
TypeDefnMap *TypeDefnMap
TypeDefnList *TypeDefnList
TypeDefnLink *TypeDefnLink
}
type TypeDefnBool struct {
}
type TypeDefnString struct {
}
type TypeDefnBytes struct {
}
type TypeDefnInt struct {
}
type TypeDefnFloat struct {
}
type TypeDefnMap struct {
KeyType string
ValueType TypeNameOrInlineDefn
ValueNullable *bool
Representation *MapRepresentation
}
type MapRepresentation struct {
MapRepresentation_Map *MapRepresentation_Map
MapRepresentation_Stringpairs *MapRepresentation_Stringpairs
MapRepresentation_Listpairs *MapRepresentation_Listpairs
}
type MapRepresentation_Map struct {
}
type MapRepresentation_Stringpairs struct {
InnerDelim string
EntryDelim string
}
type MapRepresentation_Listpairs struct {
}
type TypeDefnList struct {
ValueType TypeNameOrInlineDefn
ValueNullable *bool
Representation *ListRepresentation
}
type ListRepresentation struct {
ListRepresentation_List *ListRepresentation_List
}
type ListRepresentation_List struct {
}
type TypeDefnUnion struct {
Members List__UnionMember
Representation UnionRepresentation
}
type List__UnionMember []UnionMember
type UnionMember struct {
TypeName *string
UnionMemberInlineDefn *UnionMemberInlineDefn
}
type UnionMemberInlineDefn struct {
TypeDefnLink *TypeDefnLink
}
type List__TypeName []string
type TypeDefnLink struct {
ExpectedType *string
}
type UnionRepresentation struct {
UnionRepresentation_Kinded *UnionRepresentation_Kinded
UnionRepresentation_Keyed *UnionRepresentation_Keyed
UnionRepresentation_Envelope *UnionRepresentation_Envelope
UnionRepresentation_Inline *UnionRepresentation_Inline
UnionRepresentation_StringPrefix *UnionRepresentation_StringPrefix
UnionRepresentation_BytesPrefix *UnionRepresentation_BytesPrefix
}
type UnionRepresentation_Kinded struct {
Keys []string
Values map[string]UnionMember
}
type UnionRepresentation_Keyed struct {
Keys []string
Values map[string]UnionMember
}
type Map__String__UnionMember struct {
Keys []string
Values map[string]TypeDefn
}
type UnionRepresentation_Envelope struct {
DiscriminantKey string
ContentKey string
DiscriminantTable Map__String__UnionMember
}
type UnionRepresentation_Inline struct {
DiscriminantKey string
DiscriminantTable Map__String__TypeName
}
type UnionRepresentation_StringPrefix struct {
Prefixes Map__String__TypeName
}
type UnionRepresentation_BytesPrefix struct {
Prefixes Map__HexString__TypeName
}
type Map__HexString__TypeName struct {
Keys []string
Values map[string]string
}
type Map__String__TypeName struct {
Keys []string
Values map[string]string
}
type Map__TypeName__Int struct {
Keys []string
Values map[string]int
}
type TypeDefnStruct struct {
Fields Map__FieldName__StructField
Representation StructRepresentation
}
type Map__FieldName__StructField struct {
Keys []string
Values map[string]StructField
}
type StructField struct {
Type TypeNameOrInlineDefn
Optional *bool
Nullable *bool
}
type StructRepresentation struct {
StructRepresentation_Map *StructRepresentation_Map
StructRepresentation_Tuple *StructRepresentation_Tuple
StructRepresentation_Stringpairs *StructRepresentation_Stringpairs
StructRepresentation_Stringjoin *StructRepresentation_Stringjoin
StructRepresentation_Listpairs *StructRepresentation_Listpairs
}
type StructRepresentation_Map struct {
Fields *Map__FieldName__StructRepresentation_Map_FieldDetails
}
type Map__FieldName__StructRepresentation_Map_FieldDetails struct {
Keys []string
Values map[string]StructRepresentation_Map_FieldDetails
}
type StructRepresentation_Map_FieldDetails struct {
Rename *string
Implicit *AnyScalar
}
type StructRepresentation_Tuple struct {
FieldOrder *List__FieldName
}
type List__FieldName []string
type StructRepresentation_Stringpairs struct {
InnerDelim string
EntryDelim string
}
type StructRepresentation_Stringjoin struct {
Join string
FieldOrder *List__FieldName
}
type StructRepresentation_Listpairs struct {
}
type TypeDefnEnum struct {
Members List__EnumMember
Representation EnumRepresentation
}
type Unit struct {
}
type List__EnumMember []string
type EnumRepresentation struct {
EnumRepresentation_String *EnumRepresentation_String
EnumRepresentation_Int *EnumRepresentation_Int
}
type EnumRepresentation_String struct {
Keys []string
Values map[string]string
}
type EnumRepresentation_Int struct {
Keys []string
Values map[string]int
}
type TypeDefnUnit struct {
Representation string
}
type TypeDefnAny struct {
}
type TypeDefnCopy struct {
FromType string
}
type AnyScalar struct {
Bool *bool
String *string
Bytes *[]uint8
Int *int
Float *float64
}

View File

@@ -0,0 +1,734 @@
package schemadsl
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
dmt "github.com/ipld/go-ipld-prime/schema/dmt"
)
var globalTrue = true
// TODO: fuzz testing
func ParseBytes(src []byte) (*dmt.Schema, error) {
return Parse("", bytes.NewReader(src))
}
func ParseFile(path string) (*dmt.Schema, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return Parse(path, f)
}
func Parse(name string, r io.Reader) (*dmt.Schema, error) {
p := &parser{
path: name,
br: bufio.NewReader(r),
line: 1,
col: 1,
}
sch := &dmt.Schema{}
sch.Types.Values = make(map[string]dmt.TypeDefn)
for {
tok, err := p.consumeToken()
if err == io.EOF {
break
}
switch tok {
case "type":
name, err := p.consumeName()
if err != nil {
return nil, err
}
defn, err := p.typeDefn()
if err != nil {
return nil, err
}
mapAppend(&sch.Types, name, defn)
case "advanced":
return nil, p.errf("TODO: advanced")
default:
return nil, p.errf("unexpected token: %q", tok)
}
}
return sch, nil
}
func mapAppend(mapPtr, k, v interface{}) {
// TODO: delete with generics
// TODO: error on dupes
mval := reflect.ValueOf(mapPtr).Elem()
kval := reflect.ValueOf(k)
vval := reflect.ValueOf(v)
keys := mval.FieldByName("Keys")
keys.Set(reflect.Append(keys, kval))
values := mval.FieldByName("Values")
if values.IsNil() {
values.Set(reflect.MakeMap(values.Type()))
}
values.SetMapIndex(kval, vval)
}
type parser struct {
path string
br *bufio.Reader
peekedToken string
line, col int
}
func (p *parser) forwardError(err error) error {
var prefix string
if p.path != "" {
prefix = p.path + ":"
}
return fmt.Errorf("%s%d:%d: %s", prefix, p.line, p.col, err)
}
func (p *parser) errf(format string, args ...interface{}) error {
return p.forwardError(fmt.Errorf(format, args...))
}
func (p *parser) consumeToken() (string, error) {
if tok := p.peekedToken; tok != "" {
p.peekedToken = ""
return tok, nil
}
for {
// TODO: use runes for better unicode support
b, err := p.br.ReadByte()
if err == io.EOF {
return "", err // TODO: ErrUnexpectedEOF?
}
if err != nil {
return "", p.forwardError(err)
}
p.col++
switch b {
case ' ', '\t', '\r': // skip whitespace
continue
case '\n': // skip newline
// TODO: should we require a newline after each type def, struct field, etc?
p.line++
p.col = 1
continue
case '"': // quoted string
quoted, err := p.br.ReadString('"')
if err != nil {
return "", p.forwardError(err)
}
return "\"" + quoted, nil
case '{', '}', '[', ']', '(', ')', ':', '&': // simple token
return string(b), nil
case '#': // comment
_, err := p.br.ReadString('\n')
if err != nil {
return "", p.forwardError(err)
}
// tokenize the newline
if err := p.br.UnreadByte(); err != nil {
panic(err) // should never happen
}
continue
default: // string token or name
var sb strings.Builder
sb.WriteByte(b)
for {
b, err := p.br.ReadByte()
if err == io.EOF {
// Token ends at the end of the whole input.
return sb.String(), nil
}
if err != nil {
return "", p.forwardError(err)
}
// TODO: should probably allow unicode letters and numbers, like Go?
switch {
case b >= 'a' && b <= 'z', b >= 'A' && b <= 'Z':
case b >= '0' && b <= '9':
case b == '_':
default:
if err := p.br.UnreadByte(); err != nil {
panic(err) // should never happen
}
return sb.String(), nil
}
sb.WriteByte(b)
}
}
}
}
func (p *parser) consumePeeked() {
if p.peekedToken == "" {
panic("consumePeeked requires a peeked token to be present")
}
p.peekedToken = ""
}
func (p *parser) peekToken() (string, error) {
if tok := p.peekedToken; tok != "" {
return tok, nil
}
tok, err := p.consumeToken()
if err != nil {
if err == io.EOF {
// peekToken is often used when a token is optional.
// If we hit io.EOF, that's not an error.
// TODO: consider making peekToken just not return an error?
return "", nil
}
return "", err
}
p.peekedToken = tok
return tok, nil
}
func (p *parser) consumeName() (string, error) {
tok, err := p.consumeToken()
if err != nil {
return "", err
}
switch tok {
case "\"", "{", "}", "[", "]", "(", ")", ":":
return "", p.errf("expected a name, got %q", tok)
}
if tok[0] == '"' {
return "", p.errf("expected a name, got string %s", tok)
}
return tok, nil
}
func (p *parser) consumeString() (string, error) {
tok, err := p.consumeToken()
if err != nil {
return "", err
}
if tok[0] != '"' {
return "", p.errf("expected a string, got %q", tok)
}
// Unquote, too.
return tok[1 : len(tok)-1], nil
}
func (p *parser) consumeStringMap() (map[string]string, error) {
result := map[string]string{}
loop:
for {
tok, err := p.peekToken()
if err != nil {
return result, err
}
switch tok {
case "{":
p.consumePeeked()
case "}":
p.consumePeeked()
break loop
default:
key, err := p.consumeName()
if err != nil {
return result, err
}
value, err := p.consumeString()
if err != nil {
return result, err
}
result[key] = value
}
}
return result, nil
}
func (p *parser) consumeRequired(tok string) error {
got, err := p.consumeToken()
if err != nil {
return err
}
if got != tok {
return p.errf("expected %q, got %q", tok, got)
}
return nil
}
func (p *parser) typeDefn() (dmt.TypeDefn, error) {
var defn dmt.TypeDefn
kind, err := p.consumeToken()
if err != nil {
return defn, err
}
switch kind {
case "struct":
if err := p.consumeRequired("{"); err != nil {
return defn, err
}
defn.TypeDefnStruct, err = p.typeStruct()
case "union":
if err := p.consumeRequired("{"); err != nil {
return defn, err
}
defn.TypeDefnUnion, err = p.typeUnion()
case "enum":
if err := p.consumeRequired("{"); err != nil {
return defn, err
}
defn.TypeDefnEnum, err = p.typeEnum()
case "bool":
defn.TypeDefnBool = &dmt.TypeDefnBool{}
case "bytes":
defn.TypeDefnBytes = &dmt.TypeDefnBytes{}
case "float":
defn.TypeDefnFloat = &dmt.TypeDefnFloat{}
case "int":
defn.TypeDefnInt = &dmt.TypeDefnInt{}
case "link":
defn.TypeDefnLink = &dmt.TypeDefnLink{}
case "any":
defn.TypeDefnAny = &dmt.TypeDefnAny{}
case "&":
target, err := p.consumeName()
if err != nil {
return defn, err
}
defn.TypeDefnLink = &dmt.TypeDefnLink{ExpectedType: &target}
case "string":
defn.TypeDefnString = &dmt.TypeDefnString{}
case "{":
defn.TypeDefnMap, err = p.typeMap()
case "[":
defn.TypeDefnList, err = p.typeList()
case "=":
from, err := p.consumeName()
if err != nil {
return defn, err
}
defn.TypeDefnCopy = &dmt.TypeDefnCopy{FromType: from}
default:
err = p.errf("unknown type keyword: %q", kind)
}
return defn, err
}
func (p *parser) typeStruct() (*dmt.TypeDefnStruct, error) {
repr := &dmt.StructRepresentation_Map{}
repr.Fields = &dmt.Map__FieldName__StructRepresentation_Map_FieldDetails{}
defn := &dmt.TypeDefnStruct{}
for {
tok, err := p.consumeToken()
if err != nil {
return nil, err
}
if tok == "}" {
break
}
name := tok
var field dmt.StructField
loop:
for {
tok, err := p.peekToken()
if err != nil {
return nil, err
}
switch tok {
case "optional":
if field.Optional != nil {
return nil, p.errf("multiple optional keywords")
}
field.Optional = &globalTrue
p.consumePeeked()
case "nullable":
if field.Nullable != nil {
return nil, p.errf("multiple nullable keywords")
}
field.Nullable = &globalTrue
p.consumePeeked()
default:
var err error
field.Type, err = p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
break loop
}
}
tok, err = p.peekToken()
if err != nil {
return nil, err
}
if tok == "(" {
details := dmt.StructRepresentation_Map_FieldDetails{}
p.consumePeeked()
parenLoop:
for {
tok, err = p.consumeToken()
if err != nil {
return nil, err
}
switch tok {
case ")":
break parenLoop
case "rename":
str, err := p.consumeString()
if err != nil {
return nil, err
}
details.Rename = &str
case "implicit":
scalar, err := p.consumeToken()
if err != nil {
return nil, err
}
var anyScalar dmt.AnyScalar
switch {
case scalar[0] == '"': // string
s, err := strconv.Unquote(scalar)
if err != nil {
return nil, p.forwardError(err)
}
anyScalar.String = &s
case scalar == "true", scalar == "false": // bool
t := scalar == "true"
anyScalar.Bool = &t
case scalar[0] >= '0' && scalar[0] <= '0':
n, err := strconv.Atoi(scalar)
if err != nil {
return nil, p.forwardError(err)
}
anyScalar.Int = &n
default:
return nil, p.errf("unsupported implicit scalar: %s", scalar)
}
details.Implicit = &anyScalar
}
}
mapAppend(repr.Fields, name, details)
}
mapAppend(&defn.Fields, name, field)
}
reprName := "map" // default repr
if tok, err := p.peekToken(); err == nil && tok == "representation" {
p.consumePeeked()
name, err := p.consumeName()
if err != nil {
return nil, err
}
reprName = name
}
if reprName != "map" && len(repr.Fields.Keys) > 0 {
return nil, p.errf("rename and implicit are only supported for struct map representations")
}
switch reprName {
case "map":
if len(repr.Fields.Keys) == 0 {
// Fields is optional; omit it if empty.
repr.Fields = nil
}
defn.Representation.StructRepresentation_Map = repr
return defn, nil
case "tuple":
defn.Representation.StructRepresentation_Tuple = &dmt.StructRepresentation_Tuple{}
return defn, nil
// TODO: support custom fieldorder
case "stringjoin":
optMap, err := p.consumeStringMap()
if err != nil {
return nil, err
}
join, hasJoin := optMap["join"]
if !hasJoin {
return nil, p.errf("no join value provided for stringjoin repr")
}
defn.Representation.StructRepresentation_Stringjoin = &dmt.StructRepresentation_Stringjoin{
Join: join,
}
return defn, nil
default:
return nil, p.errf("unknown struct repr: %q", reprName)
}
}
func (p *parser) typeNameOrInlineDefn() (dmt.TypeNameOrInlineDefn, error) {
var typ dmt.TypeNameOrInlineDefn
tok, err := p.consumeToken()
if err != nil {
return typ, err
}
switch tok {
case "&":
expectedName, err := p.consumeName()
if err != nil {
return typ, err
}
typ.InlineDefn = &dmt.InlineDefn{TypeDefnLink: &dmt.TypeDefnLink{ExpectedType: &expectedName}}
case "[":
tlist, err := p.typeList()
if err != nil {
return typ, err
}
typ.InlineDefn = &dmt.InlineDefn{TypeDefnList: tlist}
case "{":
tmap, err := p.typeMap()
if err != nil {
return typ, err
}
typ.InlineDefn = &dmt.InlineDefn{TypeDefnMap: tmap}
default:
typ.TypeName = &tok
}
return typ, nil
}
func (p *parser) typeList() (*dmt.TypeDefnList, error) {
defn := &dmt.TypeDefnList{}
tok, err := p.peekToken()
if err != nil {
return nil, err
}
if tok == "nullable" {
defn.ValueNullable = &globalTrue
p.consumePeeked()
}
defn.ValueType, err = p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
if err := p.consumeRequired("]"); err != nil {
return defn, err
}
// TODO: repr
return defn, nil
}
func (p *parser) typeMap() (*dmt.TypeDefnMap, error) {
defn := &dmt.TypeDefnMap{}
var err error
defn.KeyType, err = p.consumeName()
if err != nil {
return nil, err
}
if err := p.consumeRequired(":"); err != nil {
return defn, err
}
tok, err := p.peekToken()
if err != nil {
return nil, err
}
if tok == "nullable" {
defn.ValueNullable = &globalTrue
p.consumePeeked()
}
defn.ValueType, err = p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
if err := p.consumeRequired("}"); err != nil {
return defn, err
}
return defn, nil
}
func (p *parser) typeUnion() (*dmt.TypeDefnUnion, error) {
defn := &dmt.TypeDefnUnion{}
var reprKeys []string
for {
tok, err := p.consumeToken()
if err != nil {
return nil, err
}
if tok == "}" {
break
}
if tok != "|" {
return nil, p.errf("expected %q or %q, got %q", "}", "|", tok)
}
var member dmt.UnionMember
nameOrInline, err := p.typeNameOrInlineDefn()
if err != nil {
return nil, err
}
if nameOrInline.TypeName != nil {
member.TypeName = nameOrInline.TypeName
} else {
if nameOrInline.InlineDefn.TypeDefnLink != nil {
member.UnionMemberInlineDefn = &dmt.UnionMemberInlineDefn{TypeDefnLink: nameOrInline.InlineDefn.TypeDefnLink}
} else {
return nil, p.errf("expected a name or inline link, got neither")
}
}
defn.Members = append(defn.Members, member)
key, err := p.consumeToken()
if err != nil {
return nil, err
}
reprKeys = append(reprKeys, key)
}
if err := p.consumeRequired("representation"); err != nil {
return nil, err
}
reprName, err := p.consumeName()
if err != nil {
return nil, err
}
switch reprName {
case "keyed":
repr := &dmt.UnionRepresentation_Keyed{}
for i, keyStr := range reprKeys {
key, err := strconv.Unquote(keyStr)
if err != nil {
return nil, p.forwardError(err)
}
mapAppend(repr, key, defn.Members[i])
}
defn.Representation.UnionRepresentation_Keyed = repr
case "kinded":
repr := &dmt.UnionRepresentation_Kinded{}
// TODO: verify keys are valid kinds? enum should do it for us?
for i, key := range reprKeys {
mapAppend(repr, key, defn.Members[i])
}
defn.Representation.UnionRepresentation_Kinded = repr
case "stringprefix":
repr := &dmt.UnionRepresentation_StringPrefix{
Prefixes: dmt.Map__String__TypeName{
Values: map[string]string{},
},
}
for i, key := range reprKeys {
// unquote prefix string
if len(key) < 2 || key[0] != '"' || key[len(key)-1] != '"' {
return nil, p.errf("invalid stringprefix %q", key)
}
key = key[1 : len(key)-1]
// add prefix to prefixes map
repr.Prefixes.Keys = append(repr.Prefixes.Keys, key)
repr.Prefixes.Values[key] = *defn.Members[i].TypeName
}
defn.Representation.UnionRepresentation_StringPrefix = repr
default:
return nil, p.errf("TODO: union repr %q", reprName)
}
return defn, nil
}
func (p *parser) typeEnum() (*dmt.TypeDefnEnum, error) {
defn := &dmt.TypeDefnEnum{}
var reprKeys []string
for {
tok, err := p.consumeToken()
if err != nil {
return nil, err
}
if tok == "}" {
break
}
if tok != "|" {
return nil, p.errf("expected %q or %q, got %q", "}", "|", tok)
}
name, err := p.consumeToken()
if err != nil {
return nil, err
}
defn.Members = append(defn.Members, name)
if tok, err := p.peekToken(); err == nil && tok == "(" {
p.consumePeeked()
key, err := p.consumeToken()
if err != nil {
return nil, err
}
reprKeys = append(reprKeys, key)
if err := p.consumeRequired(")"); err != nil {
return defn, err
}
} else {
reprKeys = append(reprKeys, "")
}
}
reprName := "string" // default repr
if tok, err := p.peekToken(); err == nil && tok == "representation" {
p.consumePeeked()
name, err := p.consumeName()
if err != nil {
return nil, err
}
reprName = name
}
switch reprName {
case "string":
repr := &dmt.EnumRepresentation_String{}
for i, key := range reprKeys {
if key == "" {
continue // no key; defaults to the name
}
if key[0] != '"' {
return nil, p.errf("enum string representation used with non-string key: %s", key)
}
unquoted, err := strconv.Unquote(key)
if err != nil {
return nil, p.forwardError(err)
}
mapAppend(repr, defn.Members[i], unquoted)
}
defn.Representation.EnumRepresentation_String = repr
case "int":
repr := &dmt.EnumRepresentation_Int{}
for i, key := range reprKeys {
if key[0] != '"' {
return nil, p.errf("enum int representation used with non-string key: %s", key)
}
unquoted, err := strconv.Unquote(key)
if err != nil {
return nil, p.forwardError(err)
}
parsed, err := strconv.Atoi(unquoted)
if err != nil {
return nil, p.forwardError(err)
}
mapAppend(repr, defn.Members[i], parsed)
}
defn.Representation.EnumRepresentation_Int = repr
default:
return nil, p.errf("unknown enum repr: %q", reprName)
}
return defn, nil
}

163
vendor/github.com/ipld/go-ipld-prime/schema/errors.go generated vendored Normal file
View File

@@ -0,0 +1,163 @@
package schema
import (
"fmt"
"strings"
"github.com/ipld/go-ipld-prime/datamodel"
)
// TODO: errors in this package remain somewhat slapdash.
//
// - datamodel.ErrUnmatchable is used as a catch-all in some places, and contains who-knows-what values wrapped in the Reason field.
// - sometimes this wraps things like strconv errors... and on the one hand, i'm kinda okay with that; on the other, maybe saying a bit more with types before getting to that kind of shrug would be nice.
// - we probably want to use `Type` values, right?
// - or do we: because then we probably need a `Repr bool` next to it, or lots of messages would be nonsensical.
// - this is *currently* problematic because we don't actually generate type info consts yet. Hopefully soon; but the pain, meanwhile, is... substantial.
// - "substantial" is an understatement. it makes incremental development almost impossible because stringifying error reports turn into nil pointer crashes!
// - other ipld-wide errors like `datamodel.ErrWrongKind` *sometimes* refer to a TypeName... but don't *have* to, because they also arise at the merely-datamodel level; what would we do with these?
// - it's undesirable (not to mention intensely forbidden for import cycle reasons) for those error types to refer to schema.Type.
// - if we must have TypeName treated stringily in some cases, is it really useful to use full type info in other cases -- inconsistently?
// - regardless of where we end up with this, some sort of an embed for helping deal with munging and printing this would probably be wise.
// - generally, whether you should expect an "datamodel.Err*" or a "schema.Err*" from various methods is quite unclear.
// - it's possible that we should wrap *all* schema-level errors in a single "datamodel.ErrSchemaNoMatch" error of some kind, to fix the above. (and maybe that's what ErrUnmatchable really is.) as yet undecided.
// ErrUnmatchable is the error raised when processing data with IPLD Schemas and
// finding data which cannot be matched into the schema.
// It will be returned by NodeAssemblers and NodeBuilders when they are fed unmatchable data.
// As a result, it will also often be seen returned from unmarshalling
// when unmarshalling into schema-constrained NodeAssemblers.
//
// ErrUnmatchable provides the name of the type in the schema that data couldn't be matched to,
// and wraps another error as the more detailed reason.
type ErrUnmatchable struct {
// TypeName will indicate the named type of a node the function was called on.
TypeName string
// Reason must always be present. ErrUnmatchable doesn't say much otherwise.
Reason error
}
func (e ErrUnmatchable) Error() string {
return fmt.Sprintf("matching data to schema of %s rejected: %s", e.TypeName, e.Reason)
}
// Reasonf returns a new ErrUnmatchable with a Reason field set to the Errorf of the arguments.
// It's a helper function for creating untyped error reasons without importing the fmt package.
func (e ErrUnmatchable) Reasonf(format string, a ...interface{}) ErrUnmatchable {
return ErrUnmatchable{e.TypeName, fmt.Errorf(format, a...)}
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrUnmatchable) may be used to match the type of error.
func (e ErrUnmatchable) Is(err error) bool {
_, ok := err.(ErrUnmatchable)
return ok
}
// ErrMissingRequiredField is returned when calling 'Finish' on a NodeAssembler
// for a Struct that has not has all required fields set.
type ErrMissingRequiredField struct {
Missing []string
}
func (e ErrMissingRequiredField) Error() string {
return "missing required fields: " + strings.Join(e.Missing, ",")
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrMissingRequiredField) may be used to match the type of error.
func (e ErrMissingRequiredField) Is(err error) bool {
_, ok := err.(ErrMissingRequiredField)
return ok
}
// ErrInvalidKey indicates a key is invalid for some reason.
//
// This is only possible for typed nodes; specifically, it may show up when
// handling struct types, or maps with interesting key types.
// (Other kinds of key invalidity that happen for untyped maps
// fall under ErrRepeatedMapKey or ErrWrongKind.)
// (Union types use ErrInvalidUnionDiscriminant instead of ErrInvalidKey,
// even when their representation strategy is maplike.)
type ErrInvalidKey struct {
// TypeName will indicate the named type of a node the function was called on.
TypeName string
// Key is the key that was rejected.
Key datamodel.Node
// Reason, if set, may provide details (for example, the reason a key couldn't be converted to a type).
// If absent, it'll be presumed "no such field".
// ErrUnmatchable may show up as a reason for typed maps with complex keys.
Reason error
}
func (e ErrInvalidKey) Error() string {
if e.Reason == nil {
return fmt.Sprintf("invalid key for map %s: %q: no such field", e.TypeName, e.Key)
} else {
return fmt.Sprintf("invalid key for map %s: %q: %s", e.TypeName, e.Key, e.Reason)
}
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrInvalidKey) may be used to match the type of error.
func (e ErrInvalidKey) Is(err error) bool {
_, ok := err.(ErrInvalidKey)
return ok
}
// ErrNoSuchField may be returned from lookup functions on the Node
// interface when a field is requested which doesn't exist,
// or from assigning data into on a MapAssembler for a struct
// when the key doesn't match a field name in the structure
// (or, when assigning data into a ListAssembler and the list size has
// reached out of bounds, in case of a struct with list-like representations!).
type ErrNoSuchField struct {
Type Type
Field datamodel.PathSegment
}
func (e ErrNoSuchField) Error() string {
if e.Type == nil {
return fmt.Sprintf("no such field: {typeinfomissing}.%s", e.Field)
}
return fmt.Sprintf("no such field: %s.%s", e.Type.Name(), e.Field)
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrNoSuchField) may be used to match the type of error.
func (e ErrNoSuchField) Is(err error) bool {
_, ok := err.(ErrNoSuchField)
return ok
}
// ErrNotUnionStructure means data was fed into a union assembler that can't match the union.
//
// This could have one of several reasons, which are explained in the detail text:
//
// - there are too many entries in the map;
// - the keys of critical entries aren't found;
// - keys are found that aren't any of the expected critical keys;
// - etc.
//
// TypeName is currently a string... see comments at the top of this file for
// remarks on the issues we need to address about these identifiers in errors in general.
type ErrNotUnionStructure struct {
TypeName string
Detail string
}
func (e ErrNotUnionStructure) Error() string {
return fmt.Sprintf("cannot match schema: union structure constraints for %s caused rejection: %s", e.TypeName, e.Detail)
}
// Is provides support for Go's standard errors.Is function so that
// errors.Is(yourError, ErrNotUnionStructure) may be used to match the type of error.
func (e ErrNotUnionStructure) Is(err error) bool {
_, ok := err.(ErrNotUnionStructure)
return ok
}

112
vendor/github.com/ipld/go-ipld-prime/schema/kind.go generated vendored Normal file
View File

@@ -0,0 +1,112 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// TypeKind is an enum of kind in the IPLD Schema system.
//
// Note that schema.TypeKind is distinct from datamodel.Kind!
// Schema kinds include concepts such as "struct" and "enum", which are
// concepts only introduced by the Schema layer, and not present in the
// Data Model layer.
type TypeKind uint8
const (
TypeKind_Invalid TypeKind = 0
TypeKind_Map TypeKind = '{'
TypeKind_List TypeKind = '['
TypeKind_Unit TypeKind = '1'
TypeKind_Bool TypeKind = 'b'
TypeKind_Int TypeKind = 'i'
TypeKind_Float TypeKind = 'f'
TypeKind_String TypeKind = 's'
TypeKind_Bytes TypeKind = 'x'
TypeKind_Link TypeKind = '/'
TypeKind_Struct TypeKind = '$'
TypeKind_Union TypeKind = '^'
TypeKind_Enum TypeKind = '%'
TypeKind_Any TypeKind = '?'
)
func (k TypeKind) String() string {
switch k {
case TypeKind_Invalid:
return "invalid"
case TypeKind_Map:
return "map"
case TypeKind_Any:
return "any"
case TypeKind_List:
return "list"
case TypeKind_Unit:
return "unit"
case TypeKind_Bool:
return "bool"
case TypeKind_Int:
return "int"
case TypeKind_Float:
return "float"
case TypeKind_String:
return "string"
case TypeKind_Bytes:
return "bytes"
case TypeKind_Link:
return "link"
case TypeKind_Struct:
return "struct"
case TypeKind_Union:
return "union"
case TypeKind_Enum:
return "enum"
default:
panic("invalid enumeration value!")
}
}
// ActsLike returns a constant from the datamodel.Kind enum describing what
// this schema.TypeKind acts like at the Data Model layer.
//
// Things with similar names are generally conserved
// (e.g. "map" acts like "map");
// concepts added by the schema layer have to be mapped onto something
// (e.g. "struct" acts like "map").
//
// Note that this mapping describes how a typed Node will *act*, programmatically;
// it does not necessarily describe how it will be *serialized*
// (for example, a struct will always act like a map, even if it has a tuple
// representation strategy and thus becomes a list when serialized).
func (k TypeKind) ActsLike() datamodel.Kind {
switch k {
case TypeKind_Invalid:
return datamodel.Kind_Invalid
case TypeKind_Map:
return datamodel.Kind_Map
case TypeKind_List:
return datamodel.Kind_List
case TypeKind_Unit:
return datamodel.Kind_Bool // maps to 'true'. // REVIEW: odd that this doesn't map to 'null'? // TODO this should be standardized in the specs, in a table.
case TypeKind_Bool:
return datamodel.Kind_Bool
case TypeKind_Int:
return datamodel.Kind_Int
case TypeKind_Float:
return datamodel.Kind_Float
case TypeKind_String:
return datamodel.Kind_String
case TypeKind_Bytes:
return datamodel.Kind_Bytes
case TypeKind_Link:
return datamodel.Kind_Link
case TypeKind_Struct:
return datamodel.Kind_Map // clear enough: fields are keys.
case TypeKind_Union:
return datamodel.Kind_Map
case TypeKind_Enum:
return datamodel.Kind_String // 'AsString' is the one clear thing to define.
case TypeKind_Any:
return datamodel.Kind_Invalid // TODO: maybe ActsLike should return (Kind, bool)
default:
panic("invalid enumeration value!")
}
}

9
vendor/github.com/ipld/go-ipld-prime/schema/maybe.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
package schema
type Maybe uint8
const (
Maybe_Absent = Maybe(0)
Maybe_Null = Maybe(1)
Maybe_Value = Maybe(2)
)

View File

@@ -0,0 +1,220 @@
package schema
import (
"fmt"
"github.com/ipld/go-ipld-prime/datamodel"
)
// Everything in this file is __a temporary hack__ and will be __removed__.
//
// These methods will only hang around until more of the "ast" packages are finished;
// thereafter, building schema.Type and schema.TypeSystem values will only be
// possible through first constructing a schema AST, and *then* using Reify(),
// which will validate things correctly, cycle-check, cross-link, etc.
//
// (Meanwhile, we're using these methods in the codegen prototypes.)
// These methods use Type objects as parameters when pointing to other things,
// but this is... turning out consistently problematic.
// Even when we're doing this hacky direct-call doesn't-need-to-be-serializable temp stuff,
// as written, this doesn't actually let us express cyclic things viably!
// The same initialization questions are also going to come up again when we try to make
// concrete values in the output of codegen.
// Maybe it's actually just a bad idea to have our reified Type types use Type pointers at all.
// (I will never get tired of the tongue twisters, evidently.)
// I'm not actually using that much, and it's always avoidable (it's trivial to replace with a map lookup bouncing through a 'ts' variable somewhere).
// And having the AST gen'd types be... just... the thing... sounds nice. It could save a lot of work.
// (It would mean the golang types don't tell you whether the values have been checked for global properties or not, but, eh.)
// (It's not really compatible with "Prototype and Type are the same thing for codegen'd stuff", either (or, we need more interfaces, and to *really* lean into them), but maybe that's okay.)
func SpawnTypeSystem(types ...Type) (*TypeSystem, []error) {
ts := TypeSystem{}
ts.Init()
for _, typ := range types {
ts.Accumulate(typ)
}
if errs := ts.ValidateGraph(); errs != nil {
return nil, errs
}
return &ts, nil
}
func MustTypeSystem(types ...Type) *TypeSystem {
if ts, err := SpawnTypeSystem(types...); err != nil {
panic(err)
} else {
return ts
}
}
func SpawnString(name TypeName) *TypeString {
return &TypeString{typeBase{name, nil}}
}
func SpawnBool(name TypeName) *TypeBool {
return &TypeBool{typeBase{name, nil}}
}
func SpawnInt(name TypeName) *TypeInt {
return &TypeInt{typeBase{name, nil}}
}
func SpawnFloat(name TypeName) *TypeFloat {
return &TypeFloat{typeBase{name, nil}}
}
func SpawnBytes(name TypeName) *TypeBytes {
return &TypeBytes{typeBase{name, nil}}
}
func SpawnLink(name TypeName) *TypeLink {
return &TypeLink{typeBase{name, nil}, "", false}
}
func SpawnLinkReference(name TypeName, pointsTo TypeName) *TypeLink {
return &TypeLink{typeBase{name, nil}, pointsTo, true}
}
func SpawnList(name TypeName, valueType TypeName, nullable bool) *TypeList {
return &TypeList{typeBase{name, nil}, false, valueType, nullable}
}
func SpawnMap(name TypeName, keyType TypeName, valueType TypeName, nullable bool) *TypeMap {
return &TypeMap{typeBase{name, nil}, false, keyType, valueType, nullable}
}
func SpawnAny(name TypeName) *TypeAny {
return &TypeAny{typeBase{name, nil}}
}
func SpawnStruct(name TypeName, fields []StructField, repr StructRepresentation) *TypeStruct {
v := &TypeStruct{
typeBase{name, nil},
fields,
make(map[string]StructField, len(fields)),
repr,
}
for i := range fields {
fields[i].parent = v
v.fieldsMap[fields[i].name] = fields[i]
}
switch repr.(type) {
case StructRepresentation_Stringjoin:
for _, f := range fields {
if f.IsMaybe() {
panic("neither nullable nor optional is supported on struct stringjoin representation")
}
}
case nil:
v.representation = SpawnStructRepresentationMap(nil)
}
return v
}
func SpawnStructField(name string, typ TypeName, optional bool, nullable bool) StructField {
return StructField{nil /*populated later*/, name, typ, optional, nullable}
}
func SpawnStructRepresentationMap(renames map[string]string) StructRepresentation_Map {
return StructRepresentation_Map{renames, nil}
}
func SpawnStructRepresentationMap2(renames map[string]string, implicits map[string]ImplicitValue) StructRepresentation_Map {
return StructRepresentation_Map{renames, implicits}
}
func SpawnStructRepresentationTuple() StructRepresentation_Tuple {
return StructRepresentation_Tuple{}
}
func SpawnStructRepresentationStringjoin(delim string) StructRepresentation_Stringjoin {
return StructRepresentation_Stringjoin{delim}
}
func SpawnUnion(name TypeName, members []TypeName, repr UnionRepresentation) *TypeUnion {
return &TypeUnion{typeBase{name, nil}, members, repr}
}
func SpawnUnionRepresentationKeyed(table map[string]TypeName) UnionRepresentation_Keyed {
return UnionRepresentation_Keyed{table}
}
func SpawnUnionRepresentationKinded(table map[datamodel.Kind]TypeName) UnionRepresentation_Kinded {
return UnionRepresentation_Kinded{table}
}
func SpawnUnionRepresentationStringprefix(delim string, table map[string]TypeName) UnionRepresentation_Stringprefix {
return UnionRepresentation_Stringprefix{delim, table}
}
func SpawnEnum(name TypeName, members []string, repr EnumRepresentation) *TypeEnum {
return &TypeEnum{typeBase{name, nil}, members, repr}
}
// The methods relating to TypeSystem are also mutation-heavy and placeholdery.
func (ts *TypeSystem) Init() {
ts.namedTypes = make(map[TypeName]Type)
}
func (ts *TypeSystem) Accumulate(typ Type) {
typ._Type(ts)
name := typ.Name()
if _, ok := ts.namedTypes[name]; ok {
panic(fmt.Sprintf("duplicate type name: %s", name))
}
ts.namedTypes[name] = typ
ts.names = append(ts.names, name)
}
func (ts TypeSystem) GetTypes() map[TypeName]Type {
return ts.namedTypes
}
func (ts TypeSystem) TypeByName(n string) Type {
return ts.namedTypes[n]
}
func (ts TypeSystem) Names() []TypeName {
return ts.names
}
// ValidateGraph checks that all type names referenced are defined.
//
// It does not do any other validations of individual type's sensibleness
// (that should've happened when they were created
// (although also note many of those validates are NYI,
// and are roadmapped for after we research self-hosting)).
func (ts TypeSystem) ValidateGraph() []error {
var ee []error
for tn, t := range ts.namedTypes {
switch t2 := t.(type) {
case *TypeBool,
*TypeInt,
*TypeFloat,
*TypeString,
*TypeBytes,
*TypeEnum:
continue // nothing to check: these are leaf nodes and refer to no other types.
case *TypeLink:
if !t2.hasReferencedType {
continue
}
if _, ok := ts.namedTypes[t2.referencedType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as link reference type)", tn, t2.referencedType))
}
case *TypeStruct:
for _, f := range t2.fields {
if _, ok := ts.namedTypes[f.typ]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (in field %q)", tn, f.typ, f.name))
}
}
case *TypeMap:
if _, ok := ts.namedTypes[t2.keyType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as key type)", tn, t2.keyType))
}
if _, ok := ts.namedTypes[t2.valueType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as value type)", tn, t2.valueType))
}
case *TypeList:
if _, ok := ts.namedTypes[t2.valueType]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as value type)", tn, t2.valueType))
}
case *TypeUnion:
for _, mn := range t2.members {
if _, ok := ts.namedTypes[mn]; !ok {
ee = append(ee, fmt.Errorf("type %s refers to missing type %s (as a member)", tn, mn))
}
}
}
}
return ee
}

270
vendor/github.com/ipld/go-ipld-prime/schema/type.go generated vendored Normal file
View File

@@ -0,0 +1,270 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
type TypeName = string
// 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
// TypeStruct
// 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.
// Also used to set the internal pointer back to the universe its part of.
_Type(*TypeSystem)
// Returns a pointer to the TypeSystem this Type is a member of.
TypeSystem() *TypeSystem
// 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
// Returns the TypeKind of this Type.
//
// The returned value is a 1:1 association with which of the concrete
// "schema.Type*" structs this interface can be cast to.
//
// Note that a schema.TypeKind is a different enum than datamodel.Kind;
// and furthermore, there's no strict relationship between them.
// schema.TypedNode values can be described by *two* distinct Kinds:
// one which describes how the Node itself will act,
// and another which describes how the Node presents for serialization.
// For some combinations of Type and representation strategy, one or both
// of the Kinds can be determined statically; but not always:
// it can sometimes be necessary to inspect the value quite concretely
// (e.g., `schema.TypedNode{}.Representation().Kind()`) in order to find
// out exactly how a node will be serialized! This is because some types
// can vary in representation kind based on their value (specifically,
// kinded-representation unions have this property).
TypeKind() TypeKind
// RepresentationBehavior returns a description of how the representation
// of this type will behave in terms of the IPLD Data Model.
// This property varies based on the representation strategy of a type.
//
// In one case, the representation behavior cannot be known statically,
// and varies based on the data: kinded unions have this trait.
//
// This property is used by kinded unions, which require that their members
// all have distinct representation behavior.
// (It follows that a kinded union cannot have another kinded union as a member.)
//
// You may also be interested in a related property that might have been called "TypeBehavior".
// However, this method doesn't exist, because it's a deterministic property of `TypeKind()`!
// You can use `TypeKind.ActsLike()` to get type-level behavioral information.
RepresentationBehavior() datamodel.Kind
}
var (
_ Type = &TypeBool{}
_ Type = &TypeString{}
_ Type = &TypeBytes{}
_ Type = &TypeInt{}
_ Type = &TypeFloat{}
_ Type = &TypeAny{}
_ Type = &TypeMap{}
_ Type = &TypeList{}
_ Type = &TypeLink{}
_ Type = &TypeUnion{}
_ Type = &TypeStruct{}
_ Type = &TypeEnum{}
)
type typeBase struct {
name TypeName
universe *TypeSystem
}
type TypeBool struct {
typeBase
}
type TypeString struct {
typeBase
}
type TypeBytes struct {
typeBase
}
type TypeInt struct {
typeBase
}
type TypeFloat struct {
typeBase
}
type TypeAny struct {
typeBase
}
type TypeMap struct {
typeBase
anonymous bool
keyType TypeName // must be Kind==string (e.g. Type==String|Enum).
valueType TypeName
valueNullable bool
}
type TypeList struct {
typeBase
anonymous bool
valueType TypeName
valueNullable bool
}
type TypeLink struct {
typeBase
referencedType TypeName
hasReferencedType bool
// ...?
}
type TypeUnion struct {
typeBase
// Members are listed in the order they appear in the schema.
// To find the discriminant info, you must look inside the representation; they all contain a 'table' of some kind in which the member types are the values.
// Note that multiple appearances of the same type as distinct members of the union is not possible.
// While we could do this... A: that's... odd, and nearly never called for; B: not possible with kinded mode; C: imagine the golang-native type switch! it's impossible.
// We rely on this clarity in many ways: most visibly, the type-level Node implementation for a union always uses the type names as if they were map keys! This behavior is consistent for all union representations.
members []TypeName
representation UnionRepresentation
}
type UnionRepresentation interface{ _UnionRepresentation() }
func (UnionRepresentation_Keyed) _UnionRepresentation() {}
func (UnionRepresentation_Kinded) _UnionRepresentation() {}
func (UnionRepresentation_Envelope) _UnionRepresentation() {}
func (UnionRepresentation_Inline) _UnionRepresentation() {}
func (UnionRepresentation_Stringprefix) _UnionRepresentation() {}
// A bunch of these tables in union representation might be easier to use if flipped;
// we almost always index into them by type (since that's what we have an ordered list of);
// and they're unique in both directions, so it's equally valid either way.
// The order they're currently written in matches the serial form in the schema AST.
type UnionRepresentation_Keyed struct {
table map[string]TypeName // key is user-defined freetext
}
type UnionRepresentation_Kinded struct {
table map[datamodel.Kind]TypeName
}
//lint:ignore U1000 implementation TODO
type UnionRepresentation_Envelope struct {
discriminantKey string
contentKey string
table map[string]TypeName // key is user-defined freetext
}
//lint:ignore U1000 implementation TODO
type UnionRepresentation_Inline struct {
discriminantKey string
table map[string]TypeName // key is user-defined freetext
}
type UnionRepresentation_Stringprefix struct {
delim string
table map[string]TypeName // key is user-defined freetext
}
type TypeStruct struct {
typeBase
// n.b. `Fields` is an (order-preserving!) map in the schema-schema;
// but it's a list here, with the keys denormalized into the value,
// because that's typically how we use it.
fields []StructField
fieldsMap map[string]StructField // same content, indexed for lookup.
representation StructRepresentation
}
type StructField struct {
parent *TypeStruct
name string
typ TypeName
optional bool
nullable bool
}
type StructRepresentation interface{ _StructRepresentation() }
func (StructRepresentation_Map) _StructRepresentation() {}
func (StructRepresentation_Tuple) _StructRepresentation() {}
func (StructRepresentation_StringPairs) _StructRepresentation() {}
func (StructRepresentation_Stringjoin) _StructRepresentation() {}
type StructRepresentation_Map struct {
renames map[string]string
implicits map[string]ImplicitValue
}
type StructRepresentation_Tuple struct{}
//lint:ignore U1000 implementation TODO
type StructRepresentation_StringPairs struct{ sep1, sep2 string }
type StructRepresentation_Stringjoin struct{ sep string }
type TypeEnum struct {
typeBase
members []string
representation EnumRepresentation
}
type EnumRepresentation interface{ _EnumRepresentation() }
func (EnumRepresentation_String) _EnumRepresentation() {}
func (EnumRepresentation_Int) _EnumRepresentation() {}
type EnumRepresentation_String map[string]string
type EnumRepresentation_Int map[string]int
// ImplicitValue is an sum type holding values that are implicits.
// It's not an 'Any' value because it can't be recursive
// (or to be slightly more specific, it can be one of the recursive kinds,
// but if so, only its empty value is valid here).
type ImplicitValue interface{ _ImplicitValue() }
func (ImplicitValue_EmptyList) _ImplicitValue() {}
func (ImplicitValue_EmptyMap) _ImplicitValue() {}
func (ImplicitValue_String) _ImplicitValue() {}
func (ImplicitValue_Int) _ImplicitValue() {}
func (ImplicitValue_Bool) _ImplicitValue() {}
type ImplicitValue_EmptyList struct{}
type ImplicitValue_EmptyMap struct{}
type ImplicitValue_String string
type ImplicitValue_Int int
type ImplicitValue_Bool bool

View File

@@ -0,0 +1,287 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
/* cookie-cutter standard interface stuff */
func (t *typeBase) _Type(ts *TypeSystem) {
t.universe = ts
}
func (t typeBase) TypeSystem() *TypeSystem { return t.universe }
func (t typeBase) Name() TypeName { return t.name }
func (TypeBool) TypeKind() TypeKind { return TypeKind_Bool }
func (TypeString) TypeKind() TypeKind { return TypeKind_String }
func (TypeBytes) TypeKind() TypeKind { return TypeKind_Bytes }
func (TypeInt) TypeKind() TypeKind { return TypeKind_Int }
func (TypeFloat) TypeKind() TypeKind { return TypeKind_Float }
func (TypeAny) TypeKind() TypeKind { return TypeKind_Any }
func (TypeMap) TypeKind() TypeKind { return TypeKind_Map }
func (TypeList) TypeKind() TypeKind { return TypeKind_List }
func (TypeLink) TypeKind() TypeKind { return TypeKind_Link }
func (TypeUnion) TypeKind() TypeKind { return TypeKind_Union }
func (TypeStruct) TypeKind() TypeKind { return TypeKind_Struct }
func (TypeEnum) TypeKind() TypeKind { return TypeKind_Enum }
func (TypeBool) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Bool }
func (TypeString) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_String }
func (TypeBytes) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Bytes }
func (TypeInt) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Int }
func (TypeFloat) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Float }
func (TypeMap) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Map }
func (TypeList) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_List }
func (TypeLink) RepresentationBehavior() datamodel.Kind { return datamodel.Kind_Link }
func (t TypeUnion) RepresentationBehavior() datamodel.Kind {
switch t.representation.(type) {
case UnionRepresentation_Keyed:
return datamodel.Kind_Map
case UnionRepresentation_Kinded:
return datamodel.Kind_Invalid // you can't know with this one, until you see the value (and thus can its inhabitant's behavior)!
case UnionRepresentation_Envelope:
return datamodel.Kind_Map
case UnionRepresentation_Inline:
return datamodel.Kind_Map
case UnionRepresentation_Stringprefix:
return datamodel.Kind_String
default:
panic("unreachable")
}
}
func (t TypeStruct) RepresentationBehavior() datamodel.Kind {
switch t.representation.(type) {
case StructRepresentation_Map:
return datamodel.Kind_Map
case StructRepresentation_Tuple:
return datamodel.Kind_List
case StructRepresentation_StringPairs:
return datamodel.Kind_String
case StructRepresentation_Stringjoin:
return datamodel.Kind_String
default:
panic("unreachable")
}
}
func (t TypeEnum) RepresentationBehavior() datamodel.Kind {
// TODO: this should have a representation strategy switch too; sometimes that will indicate int representation behavior.
return datamodel.Kind_String
}
func (t TypeAny) RepresentationBehavior() datamodel.Kind {
return datamodel.Kind_Invalid // TODO: what can we possibly do here?
}
/* interesting methods per Type type */
// beware: many of these methods will change when we successfully bootstrap self-hosting.
//
// The current methods return reified Type objects; in the future, there might be less of that.
// Returning reified Type objects requires bouncing lookups through the typesystem map;
// this is unavoidable because we need to handle cycles in definitions.
// However, the extra (and cyclic) pointers that requires won't necessarily jive well if
// we remake the Type types to have close resemblances to the Data Model tree data.)
//
// It's also unfortunate that some of the current methods collide in name with
// the names of the Data Model fields. We might reshuffling things to reduce this.
//
// At any rate, all of these changes will come as a sweep once we
// get a self-hosting gen of the schema-schema, not before
// (the effort of updating template references is substantial).
// 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.universe.namedTypes[t.keyType]
}
// ValueType returns the Type of the map values.
func (t TypeMap) ValueType() Type {
return t.universe.namedTypes[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.universe.namedTypes[t.valueType]
}
// ValueIsNullable returns a bool describing if the list values are permitted
// to be null.
func (t TypeList) ValueIsNullable() bool {
return t.valueNullable
}
// Members returns the list of all types that are possible inhabitants of this union.
func (t TypeUnion) Members() []Type {
a := make([]Type, len(t.members))
for i := range t.members {
a[i] = t.universe.namedTypes[t.members[i]]
}
return a
}
func (t TypeUnion) RepresentationStrategy() UnionRepresentation {
return t.representation
}
func (r UnionRepresentation_Keyed) GetDiscriminant(t Type) string {
for d, t2 := range r.table {
if t2 == t.Name() {
return d
}
}
panic("that type isn't a member of this union")
}
func (r UnionRepresentation_Stringprefix) GetDelim() string {
return r.delim
}
func (r UnionRepresentation_Stringprefix) GetDiscriminant(t Type) string {
for d, t2 := range r.table {
if t2 == t.Name() {
return d
}
}
panic("that type isn't a member of this union")
}
// GetMember returns type info for the member matching the kind argument,
// or may return nil if that kind is not mapped to a member of this union.
func (r UnionRepresentation_Kinded) GetMember(k datamodel.Kind) TypeName {
return r.table[k]
}
// Fields returns a slice of descriptions of the object's fields.
func (t TypeStruct) Fields() []StructField {
return t.fields
}
// Field looks up a StructField by name, or returns nil if no such field.
func (t TypeStruct) Field(name string) *StructField {
if v, ok := t.fieldsMap[name]; ok {
return &v
}
return nil
}
// Parent returns the type information that this field describes a part of.
//
// While in many cases, you may know the parent already from context,
// there may still be situations where want to pass around a field and
// not need to continue passing down the parent type with it; this method
// helps your code be less redundant in such a situation.
// (You'll find this useful for looking up any rename directives, for example,
// when holding onto a field, since that requires looking up information from
// the representation strategy, which is a property of the type as a whole.)
func (f StructField) Parent() *TypeStruct { return f.parent }
// 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 StructField) 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 StructField) Type() Type { return f.parent.universe.namedTypes[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 StructField) 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 StructField) IsNullable() bool { return f.nullable }
// IsMaybe returns true if the field value is allowed to be either null or absent.
//
// This is a simple "or" of the two properties,
// but this method is a shorthand that turns out useful often.
func (f StructField) IsMaybe() bool { return f.nullable || f.optional }
func (t TypeStruct) RepresentationStrategy() StructRepresentation {
return t.representation
}
func (r StructRepresentation_Map) GetFieldKey(field StructField) string {
if n, ok := r.renames[field.name]; ok {
return n
}
return field.name
}
func (r StructRepresentation_Map) FieldHasRename(field StructField) bool {
_, ok := r.renames[field.name]
return ok
}
// FieldImplicit returns the 'implicit' value for a field, or nil, if there isn't one.
//
// Because this returns the golang ImplicitValue type, which is an interface,
// golang type switching is needed to distinguish what it holds.
// (In other words, be warned that this function is not very friendly to use from templating engines.)
func (r StructRepresentation_Map) FieldImplicit(field StructField) ImplicitValue {
if r.implicits == nil {
return nil
}
return r.implicits[field.name]
}
func (r StructRepresentation_Stringjoin) GetDelim() string {
return r.sep
}
// Members returns a slice the strings which are valid inhabitants of this enum.
func (t TypeEnum) Members() []string {
return t.members
}
func (t TypeEnum) RepresentationStrategy() EnumRepresentation {
return t.representation
}
// Links can keep a referenced type, which is a hint only about the data on the
// other side of the link, no something that can be explicitly validated without
// loading the link
// HasReferencedType returns true if the link has a hint about the type it references
// false if it's generic
func (t TypeLink) HasReferencedType() bool {
return t.hasReferencedType
}
// ReferencedType returns the type hint for the node on the other side of the link
func (t TypeLink) ReferencedType() Type {
return t.universe.namedTypes[t.referencedType]
}

View File

@@ -0,0 +1,85 @@
package schema
import (
"github.com/ipld/go-ipld-prime/datamodel"
)
// schema.TypedNode is a superset of the datamodel.Node interface, and has additional behaviors.
//
// A schema.TypedNode can be inspected for its schema.Type and schema.TypeKind,
// which conveys much more and richer information than the Data Model layer
// datamodel.Kind.
//
// There are many different implementations of schema.TypedNode.
// One implementation can wrap any other existing datamodel.Node (i.e., it's zero-copy)
// and promises that it has *already* been validated to match the typesystem.Type;
// another implementation similarly wraps any other existing datamodel.Node, but
// defers to the typesystem validation checking to fields that are accessed;
// and when using code generation tools, all of the generated native Golang
// types produced by the codegen will each individually implement schema.TypedNode.
//
// Typed nodes sometimes have slightly different behaviors than plain nodes:
// For example, when looking up fields on a typed node that's a struct,
// the error returned for a lookup with a key that's not a field name will
// be ErrNoSuchField (instead of ErrNotExists).
// These behaviors apply to the schema.TypedNode only and not their representations;
// continuing the example, the .Representation().LookupByString() method on
// that same node for the same key as plain `.LookupByString()` will still
// return ErrNotExists, because the representation isn't a schema.TypedNode!
type TypedNode interface {
// schema.TypedNode acts just like a regular Node for almost all purposes;
// which datamodel.Kind it acts as is determined by the TypeKind.
// (Note that the representation strategy of the type does *not* affect
// the Kind of schema.TypedNode -- rather, the representation strategy
// affects the `.Representation().Kind()`.)
//
// For example: if the `.Type().TypeKind()` of this node is "struct",
// it will act like Kind() == "map"
// (even if Type().(Struct).ReprStrategy() is "tuple").
datamodel.Node
// Type returns a reference to the reified schema.Type value.
Type() Type
// Representation returns a datamodel.Node which sees the data in this node
// in its representation form.
//
// For example: if the `.Type().TypeKind()` of this node is "struct",
// `.Representation().TypeKind()` may vary based on its representation strategy:
// if the representation strategy is "map", then it will be Kind=="map";
// if the streatgy is "tuple", then it will be Kind=="list".
Representation() datamodel.Node
}
// schema.TypedLinkNode is a superset of the schema.TypedNode interface, and has one additional behavior.
//
// A schema.TypedLinkNode contains a hint for the appropriate node builder to use for loading data
// on the other side of the link contained within the node, so that it can be assembled
// into a node representation and validated against the schema as quickly as possible
//
// So, for example, if you wanted to support loading the other side of a link
// with a code-gen'd node builder while utilizing the automatic loading facilities
// of the traversal package, you could write a LinkNodeBuilderChooser as follows:
//
// func LinkNodeBuilderChooser(lnk datamodel.Link, lnkCtx linking.LinkContext) datamodel.NodePrototype {
// if tlnkNd, ok := lnkCtx.LinkNode.(schema.TypedLinkNode); ok {
// return tlnkNd.LinkTargetNodePrototype()
// }
// return basicnode.Prototype.Any
// }
type TypedLinkNode interface {
LinkTargetNodePrototype() datamodel.NodePrototype
}
// TypedPrototype is a superset of the datamodel.Nodeprototype interface, and has
// additional behaviors, much like TypedNode for datamodel.Node.
type TypedPrototype interface {
datamodel.NodePrototype
// Type returns a reference to the reified schema.Type value.
Type() Type
// Representation returns a datamodel.NodePrototype for the representation
// form of the prototype.
Representation() datamodel.NodePrototype
}

View File

@@ -0,0 +1,18 @@
package schema
type TypeSystem struct {
// namedTypes is the set of all named types in this universe.
// The map's key is the value's Name() property and must be unique.
//
// The IsAnonymous property is false for all values in this map that
// support the IsAnonymous property.
//
// Each Type in the universe may only refer to other types in their
// definition if those type are either A) in this namedTypes map,
// or B) are IsAnonymous==true.
namedTypes map[TypeName]Type
// names are the same set of names stored in namedTypes,
// but in insertion order.
names []TypeName
}

View File

@@ -0,0 +1,70 @@
package schema
/*
Okay, so. There are several fun considerations for a "validate" method.
---
There's two radically different approaches to "validate"/"reify":
- Option 1: Look at the schema.Type info and check if a data node seems
to match it -- recursing on the type info.
- Option 2: Use the schema.Type{}.RepresentationNodeBuilder() to feed data
into it -- recursing on what the nodebuilder already expresses.
(Option 2 also need to take a `memStorage ipld.NodeBuilder` param, btw,
for handling all the cases where we *aren't* doing codegen.)
Option 1 provides a little more opportunity for returning multiple errors.
Option 2 will generally have a hard time with that (nodebuilers are not
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
at least two very different functions -- despite seeming to do similar
things, their interior will radically diverge.
---
We may also need to consider distinct reification paths: we may want one
that returns a new node tree which is eagerly converted to schema.TypedNode
recursively; and another that returns a lazyNode which wraps things
with their typed node constraints only as they're requested.
(Note that the latter would have interesting implications for any code
which has expectations about pointer equality consistency.)
---
A further fun issue which needs consideration: well, I'll just save a snip
of prospective docs I wrote while trying to iterate on these functions:
// Note that using Validate on a node that's already a schema.TypedNode is likely
// to be nonsensical. In many schemas, the schema.TypedNode tree is actually a
// different depth than its representational tree (e.g. unions can cause this),
... and that's ... that's a fairly sizable issue that needs resolving.
There's a couple of different ways to handle some of the behaviors around
unions, and some of them make the tradeoff described above, and I'm really
unsure if all the implications have been sussed out yet. We should defer
writing code that depends on this issue until gathering some more info.
---
One more note: about returning multiple errors from a Validate function:
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,
upon encountering a union and failing to match it, we can't generally
produce further errors from anywhere deeper in the tree without them being
combinatorial "if previous juncture X was type Y, then..." nonsense.
(This applies to all recursive kinds to some degree, but it's especially
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.)
---
And finally: both "Validate" and "Reify" methods might actually belong
in the schema.TypedNode package -- if they make *any* reference to `schema.TypedNode`,
then they have no choice (otherwise, cyclic imports would occur).
If we make a "Validate" that works purely on the schema.Type info, and
returns *only* errors: only then we can have it in the schema package.
*/