From 8f7edabccb4525171b2bd9077d9cf86b7689b0c8 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 1 Aug 2024 22:54:02 -0700 Subject: [PATCH] feat(schema): expose useful functions for building schemas add more minimal form of schema compilation and useful functions for merging one type system into another. fix #568 --- schema/dmt/compile.go | 53 ++++++++++++-------------- schema/tmpBuilders.go | 87 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 29 deletions(-) diff --git a/schema/dmt/compile.go b/schema/dmt/compile.go index afb4ee15..8f0f7bd9 100644 --- a/schema/dmt/compile.go +++ b/schema/dmt/compile.go @@ -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, @@ -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 { diff --git a/schema/tmpBuilders.go b/schema/tmpBuilders.go index affbab81..80914ce1 100644 --- a/schema/tmpBuilders.go +++ b/schema/tmpBuilders.go @@ -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() {