diff --git a/schema/scope.go b/schema/scope.go index 53eda03..729d3f3 100644 --- a/schema/scope.go +++ b/schema/scope.go @@ -13,6 +13,7 @@ type Scope interface { Object Objects() map[string]*ObjectSchema Root() string + RootObject() *ObjectSchema SelfSerialize() (any, error) } @@ -56,40 +57,40 @@ func (s *ScopeSchema) SelfSerialize() (any, error) { } func (s *ScopeSchema) ID() string { - return s.ObjectsValue[s.RootValue].ID() + return s.RootObject().ID() } func (s *ScopeSchema) Properties() map[string]*PropertySchema { - return s.ObjectsValue[s.RootValue].PropertiesValue + return s.RootObject().PropertiesValue } func (s *ScopeSchema) GetDefaults() map[string]any { - return s.ObjectsValue[s.RootValue].GetDefaults() + return s.RootObject().GetDefaults() } func (s *ScopeSchema) ReflectedType() reflect.Type { - return s.ObjectsValue[s.RootValue].ReflectedType() + return s.RootObject().ReflectedType() } func (s *ScopeSchema) Unserialize(data any) (any, error) { - return s.ObjectsValue[s.RootValue].Unserialize(data) + return s.RootObject().Unserialize(data) } func (s *ScopeSchema) ValidateCompatibility(typeOrData any) error { schemaType, ok := typeOrData.(*ScopeSchema) if ok { - return s.ObjectsValue[s.RootValue].ValidateCompatibility(schemaType.ObjectsValue[schemaType.RootValue]) + return s.RootObject().ValidateCompatibility(schemaType.ObjectsValue[schemaType.RootValue]) } - return s.ObjectsValue[s.RootValue].ValidateCompatibility(typeOrData) + return s.RootObject().ValidateCompatibility(typeOrData) } func (s *ScopeSchema) Validate(data any) error { - return s.ObjectsValue[s.RootValue].Validate(data) + return s.RootObject().Validate(data) } func (s *ScopeSchema) Serialize(data any) (any, error) { - return s.ObjectsValue[s.RootValue].Serialize(data) + return s.RootObject().Serialize(data) } func (s *ScopeSchema) ApplyScope(externalScope Scope, namespace string) { @@ -123,6 +124,38 @@ func (s *ScopeSchema) Objects() map[string]*ObjectSchema { return s.ObjectsValue } +func (s *ScopeSchema) objectIDList(separator string) string { + output := "" + for id := range s.ObjectsValue { + output += separator + id + } + return output +} + +func (s *ScopeSchema) RootObject() *ObjectSchema { + rootObject, rootObjectFound := s.ObjectsValue[s.RootValue] + if !rootObjectFound { + panic(fmt.Sprintf( + "root object with ID %q not found; available objects:%s", + s.RootValue, + s.objectIDList("\n\t"), + )) + } + if rootObject == nil { + panic(fmt.Sprintf( + "root object with ID %q is nil", + s.RootValue, + )) + } + if rootObject.ID() != s.RootValue { + panic(fmt.Sprintf( + "root object's ID %q doesn't match its map key %q; please fix the schema definition", + rootObject.ID(), s.RootValue, + )) + } + return rootObject +} + func (s *ScopeSchema) Root() string { return s.RootValue } diff --git a/schema/scope_test.go b/schema/scope_test.go index 7f8eebe..2c4bcc6 100644 --- a/schema/scope_test.go +++ b/schema/scope_test.go @@ -498,3 +498,67 @@ func TestApplyingExternalNamespaceToNonRefTypes(t *testing.T) { }) } } + +func TestGetRootObject(t *testing.T) { + rootObject := schema.NewObjectSchema("a", map[string]*schema.PropertySchema{}) + correctSchema := schema.ScopeSchema{ + ObjectsValue: map[string]*schema.ObjectSchema{ + "a": rootObject, + }, + RootValue: "a", + } + assert.Equals(t, correctSchema.RootObject(), rootObject) +} + +func TestMismatchedRoot(t *testing.T) { + // This is a common user mistake: invalid RootValue in the scope. + // Panic when a Scope's RootValue is not a key in its ObjectsValue + brokenSchema := schema.ScopeSchema{ + ObjectsValue: map[string]*schema.ObjectSchema{ + "a": schema.NewObjectSchema("a", map[string]*schema.PropertySchema{}), + }, + RootValue: "wrong", + } + assert.PanicsContains(t, func() { + brokenSchema.RootObject() + }, "root object with ID \"wrong\" not found; available objects:\n\ta") +} + +func TestMismatchedRootKey(t *testing.T) { + // Tests when the Root value and ID value are correct, but the key of the object map is wrong. + brokenSchema := schema.ScopeSchema{ + ObjectsValue: map[string]*schema.ObjectSchema{ + "wrong": schema.NewObjectSchema("a", map[string]*schema.PropertySchema{}), + }, + RootValue: "a", + } + assert.PanicsContains(t, func() { + brokenSchema.RootObject() + }, "root object with ID \"a\" not found; available objects:\n\twrong") +} + +func TestNilRoot(t *testing.T) { + // This is just a bug case; nil object in the objects map. + brokenSchema := schema.ScopeSchema{ + ObjectsValue: map[string]*schema.ObjectSchema{ + "a": nil, + }, + RootValue: "a", + } + assert.PanicsContains(t, func() { + brokenSchema.RootObject() + }, "root object with ID \"a\" is nil") +} + +func TestMismatchedRootID(t *testing.T) { + // This is a common user mistake: valid scope map key doesn't match the object's ID + brokenSchema := schema.ScopeSchema{ + ObjectsValue: map[string]*schema.ObjectSchema{ + "wrong": schema.NewObjectSchema("a", map[string]*schema.PropertySchema{}), + }, + RootValue: "wrong", + } + assert.PanicsContains(t, func() { + brokenSchema.RootObject() + }, "root object's ID \"a\" doesn't match its map key \"wrong\"") +}