201 lines
4.9 KiB
Go
201 lines
4.9 KiB
Go
package schema
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// ModuleSchema represents the logical schema of a module for purposes of indexing and querying.
|
|
type ModuleSchema struct {
|
|
types map[string]Type
|
|
}
|
|
|
|
// CompileModuleSchema compiles the types into a ModuleSchema and validates it.
|
|
// Any module schema returned without an error is guaranteed to be valid.
|
|
func CompileModuleSchema(types ...Type) (ModuleSchema, error) {
|
|
typeMap := map[string]Type{}
|
|
|
|
for _, typ := range types {
|
|
if _, ok := typeMap[typ.TypeName()]; ok {
|
|
return ModuleSchema{}, fmt.Errorf("duplicate type %q", typ.TypeName())
|
|
}
|
|
|
|
typeMap[typ.TypeName()] = typ
|
|
}
|
|
|
|
res := ModuleSchema{types: typeMap}
|
|
|
|
err := res.Validate()
|
|
if err != nil {
|
|
return ModuleSchema{}, err
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// MustCompileModuleSchema constructs a new ModuleSchema and panics if it is invalid.
|
|
// This should only be used in test code or static initialization where it is safe to panic!
|
|
func MustCompileModuleSchema(types ...Type) ModuleSchema {
|
|
sch, err := CompileModuleSchema(types...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return sch
|
|
}
|
|
|
|
// Validate validates the module schema.
|
|
func (s ModuleSchema) Validate() error {
|
|
for _, typ := range s.types {
|
|
err := typ.Validate(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateObjectUpdate validates that the update conforms to the module schema.
|
|
func (s ModuleSchema) ValidateObjectUpdate(update StateObjectUpdate) error {
|
|
typ, ok := s.types[update.TypeName]
|
|
if !ok {
|
|
return fmt.Errorf("object type %q not found in module schema", update.TypeName)
|
|
}
|
|
|
|
objTyp, ok := typ.(StateObjectType)
|
|
if !ok {
|
|
return fmt.Errorf("type %q is not an object type", update.TypeName)
|
|
}
|
|
|
|
return objTyp.ValidateObjectUpdate(update, s)
|
|
}
|
|
|
|
// LookupType looks up a type by name in the module schema.
|
|
func (s ModuleSchema) LookupType(name string) (Type, bool) {
|
|
typ, ok := s.types[name]
|
|
return typ, ok
|
|
}
|
|
|
|
// LookupEnumType is a convenience method that looks up an EnumType by name.
|
|
func (s ModuleSchema) LookupEnumType(name string) (t EnumType, found bool) {
|
|
typ, found := s.LookupType(name)
|
|
if !found {
|
|
return EnumType{}, false
|
|
}
|
|
t, ok := typ.(EnumType)
|
|
if !ok {
|
|
return EnumType{}, false
|
|
}
|
|
return t, true
|
|
}
|
|
|
|
// LookupObjectType is a convenience method that looks up an ObjectType by name.
|
|
func (s ModuleSchema) LookupStateObjectType(name string) (t StateObjectType, found bool) {
|
|
typ, found := s.LookupType(name)
|
|
if !found {
|
|
return StateObjectType{}, false
|
|
}
|
|
t, ok := typ.(StateObjectType)
|
|
if !ok {
|
|
return StateObjectType{}, false
|
|
}
|
|
return t, true
|
|
}
|
|
|
|
// AllTypes calls the provided function for each type in the module schema and stops if the function returns false.
|
|
// The types are iterated over in sorted order by name. This function is compatible with go 1.23 iterators.
|
|
func (s ModuleSchema) AllTypes(f func(Type) bool) {
|
|
keys := make([]string, 0, len(s.types))
|
|
for k := range s.types {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
if !f(s.types[k]) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// ObjectTypes iterators over all the object types in the schema in alphabetical order.
|
|
func (s ModuleSchema) StateObjectTypes(f func(StateObjectType) bool) {
|
|
s.AllTypes(func(t Type) bool {
|
|
objTyp, ok := t.(StateObjectType)
|
|
if ok {
|
|
return f(objTyp)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
// EnumTypes iterators over all the enum types in the schema in alphabetical order.
|
|
func (s ModuleSchema) EnumTypes(f func(EnumType) bool) {
|
|
s.AllTypes(func(t Type) bool {
|
|
enumType, ok := t.(EnumType)
|
|
if ok {
|
|
return f(enumType)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
type moduleSchemaJson struct {
|
|
ObjectTypes []StateObjectType `json:"object_types"`
|
|
EnumTypes []EnumType `json:"enum_types"`
|
|
}
|
|
|
|
// MarshalJSON implements the json.Marshaler interface for ModuleSchema.
|
|
// It marshals the module schema into a JSON object with the object types and enum types
|
|
// under the keys "object_types" and "enum_types" respectively.
|
|
func (s ModuleSchema) MarshalJSON() ([]byte, error) {
|
|
asJson := moduleSchemaJson{}
|
|
|
|
s.StateObjectTypes(func(objType StateObjectType) bool {
|
|
asJson.ObjectTypes = append(asJson.ObjectTypes, objType)
|
|
return true
|
|
})
|
|
|
|
s.EnumTypes(func(enumType EnumType) bool {
|
|
asJson.EnumTypes = append(asJson.EnumTypes, enumType)
|
|
return true
|
|
})
|
|
|
|
return json.Marshal(asJson)
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface for ModuleSchema.
|
|
// See MarshalJSON for the JSON format.
|
|
func (s *ModuleSchema) UnmarshalJSON(data []byte) error {
|
|
asJson := moduleSchemaJson{}
|
|
|
|
err := json.Unmarshal(data, &asJson)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
types := map[string]Type{}
|
|
|
|
for _, objType := range asJson.ObjectTypes {
|
|
types[objType.Name] = objType
|
|
}
|
|
|
|
for _, enumType := range asJson.EnumTypes {
|
|
types[enumType.Name] = enumType
|
|
}
|
|
|
|
s.types = types
|
|
|
|
// validate adds all enum types to the type map
|
|
err = s.Validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ModuleSchema) isTypeSet() {}
|
|
|
|
var _ TypeSet = ModuleSchema{}
|