Skip to content

Commit

Permalink
feat(schema): expose useful functions for building schemas (#569)
Browse files Browse the repository at this point in the history
add more minimal form of schema compilation and useful functions for merging one type system into
another.

fix #568
  • Loading branch information
hannahhoward authored Aug 5, 2024
1 parent 198d7db commit 36adac0
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 29 deletions.
53 changes: 24 additions & 29 deletions schema/dmt/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,14 @@ import (
// 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"))
// Add basic types
schema.SpawnDefaultBasicTypes(ts)

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))
// Add the schema types
err := SpawnSchemaTypes(ts, node)
if err != nil {
return err
}

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,
Expand All @@ -73,6 +50,24 @@ func Compile(ts *schema.TypeSystem, node *Schema) error {
return nil
}

// SpawnSchemaTypes is a lighter verion of compile that doesn't add basic types and doesn't validate the graph --
// for use when you want to build a type system from multiple sources and validate later
func SpawnSchemaTypes(ts *schema.TypeSystem, node *Schema) error {

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)
}

return nil
}

// Note that the parser and compiler support defaults. We're lacking support in bindnode.
func todoFromImplicitlyFalseBool(b *bool) bool {
if b == nil {
Expand Down
87 changes: 87 additions & 0 deletions schema/tmpBuilders.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,93 @@ func SpawnEnum(name TypeName, members []string, repr EnumRepresentation) *TypeEn
return &TypeEnum{typeBase{name, nil}, members, repr}
}

// Utility function adding default basic types to schema type system
func SpawnDefaultBasicTypes(ts *TypeSystem) {
ts.Accumulate(SpawnBool("Bool"))
ts.Accumulate(SpawnInt("Int"))
ts.Accumulate(SpawnFloat("Float"))
ts.Accumulate(SpawnString("String"))
ts.Accumulate(SpawnBytes("Bytes"))

ts.Accumulate(SpawnAny("Any"))

ts.Accumulate(SpawnMap("Map", "String", "Any", false))
ts.Accumulate(SpawnList("List", "Any", false))

// Should be &Any, really.
ts.Accumulate(SpawnLink("Link"))

// TODO: schema package lacks support?
// ts.Accumulate(schema.SpawnUnit("Null", NullRepr))
}

// Clone creates a copy of a type that is not in the original's universe (so it can be used elsewhere safely)
func Clone(typ Type) Type {
switch kindedType := typ.(type) {
case *TypeBool:
return SpawnBool(kindedType.Name())
case *TypeString:
return SpawnString(kindedType.Name())
case *TypeBytes:
return SpawnBytes(kindedType.Name())
case *TypeInt:
return SpawnInt(kindedType.Name())
case *TypeFloat:
return SpawnFloat(kindedType.Name())
case *TypeAny:
return SpawnAny(kindedType.Name())
case *TypeMap:
return SpawnMap(kindedType.Name(),
kindedType.KeyType().Name(),
kindedType.ValueType().Name(),
kindedType.ValueIsNullable())
case *TypeList:
return SpawnList(kindedType.Name(), kindedType.ValueType().Name(), kindedType.ValueIsNullable())
case *TypeLink:
if kindedType.HasReferencedType() {
return SpawnLinkReference(kindedType.Name(), kindedType.ReferencedType().Name())
} else {
return SpawnLink(kindedType.Name())
}
case *TypeUnion:
members := kindedType.Members()
memberNames := make([]TypeName, 0, len(members))
for _, member := range members {
memberNames = append(memberNames, member.Name())
}
return SpawnUnion(kindedType.Name(), memberNames, kindedType.RepresentationStrategy())
case *TypeStruct:
oldFields := kindedType.Fields()
newFields := make([]StructField, 0, len(oldFields))
for _, oldField := range oldFields {
newFields = append(newFields, SpawnStructField(oldField.Name(), oldField.Type().Name(), oldField.IsOptional(), oldField.IsNullable()))
}
return SpawnStruct(kindedType.Name(), newFields, kindedType.RepresentationStrategy())
case *TypeEnum:
return SpawnEnum(kindedType.Name(), kindedType.Members(), kindedType.RepresentationStrategy())
default:
panic("unexpected type, don't know how to clone")
}
}

func MergeTypeSystem(target *TypeSystem, source *TypeSystem, ignoreDups bool) {
for _, name := range source.Names() {
typ := Clone(source.TypeByName(name))
if ignoreDups {
accumulateWithRecovery(target, typ)
} else {
target.Accumulate(typ)
}
}
}

func accumulateWithRecovery(ts *TypeSystem, typ Type) {
defer func() {
_ = recover()
}()
ts.Accumulate(typ)
}

// The methods relating to TypeSystem are also mutation-heavy and placeholdery.

func (ts *TypeSystem) Init() {
Expand Down

0 comments on commit 36adac0

Please sign in to comment.