diff --git a/libs/jsonschema/instance.go b/libs/jsonschema/instance.go index 0b060cff..da7453ec 100644 --- a/libs/jsonschema/instance.go +++ b/libs/jsonschema/instance.go @@ -47,6 +47,8 @@ func (s *Schema) ValidateInstance(instance map[string]any) error { s.validateRequired, s.validateTypes, s.validatePattern, + s.validateConst, + s.validateAnyOf, } for _, fn := range validations { @@ -129,3 +131,32 @@ func (s *Schema) validatePattern(instance map[string]any) error { } return nil } + +func (s *Schema) validateConst(instance map[string]any) error { + for name, property := range s.Properties { + if property.Const == nil { + continue + } + v, ok := instance[name] + if !ok { + return fmt.Errorf("property %s has const set to %v but no value was provided", name, property.Const) + } + if v != property.Const { + return fmt.Errorf("expected value of property %s to be %v. Found: %v", name, property.Const, v) + } + } + return nil +} + +func (s *Schema) validateAnyOf(instance map[string]any) error { + if s.AnyOf == nil { + return nil + } + for _, anyOf := range s.AnyOf { + err := anyOf.validateConst(instance) + if err == nil { + return nil + } + } + return fmt.Errorf("instance does not match any of the schemas in anyOf") +} diff --git a/libs/jsonschema/instance_test.go b/libs/jsonschema/instance_test.go index 8edbf796..9a2c8048 100644 --- a/libs/jsonschema/instance_test.go +++ b/libs/jsonschema/instance_test.go @@ -222,3 +222,92 @@ func TestValidateInstanceForMultiplePatterns(t *testing.T) { assert.EqualError(t, schema.validatePattern(invalidInstanceValue), "invalid value for bar: \"xyz\". Expected to match regex pattern: ^[d-f]+$") assert.EqualError(t, schema.ValidateInstance(invalidInstanceValue), "invalid value for bar: \"xyz\". Expected to match regex pattern: ^[d-f]+$") } + +func TestValidateInstanceForConst(t *testing.T) { + schema, err := Load("./testdata/instance-validate/test-schema-const.json") + require.NoError(t, err) + + // Valid values for both foo and bar + validInstance := map[string]any{ + "foo": "abc", + "bar": "def", + } + assert.NoError(t, schema.validateConst(validInstance)) + assert.NoError(t, schema.ValidateInstance(validInstance)) + + // Empty instance + emptyInstanceValue := map[string]any{} + assert.ErrorContains(t, schema.validateConst(emptyInstanceValue), "but no value was provided") + assert.ErrorContains(t, schema.ValidateInstance(emptyInstanceValue), "but no value was provided") + + // Missing value for bar + missingInstanceValue := map[string]any{ + "foo": "abc", + } + assert.EqualError(t, schema.validateConst(missingInstanceValue), "property bar has const set to def but no value was provided") + assert.EqualError(t, schema.ValidateInstance(missingInstanceValue), "property bar has const set to def but no value was provided") + + // Valid value for bar, invalid value for foo + invalidInstanceValue := map[string]any{ + "foo": "xyz", + "bar": "def", + } + assert.EqualError(t, schema.validateConst(invalidInstanceValue), "expected value of property foo to be abc. Found: xyz") + assert.EqualError(t, schema.ValidateInstance(invalidInstanceValue), "expected value of property foo to be abc. Found: xyz") + + // Valid value for foo, invalid value for bar + invalidInstanceValue = map[string]any{ + "foo": "abc", + "bar": "xyz", + } + assert.EqualError(t, schema.validateConst(invalidInstanceValue), "expected value of property bar to be def. Found: xyz") + assert.EqualError(t, schema.ValidateInstance(invalidInstanceValue), "expected value of property bar to be def. Found: xyz") +} + +func TestValidateInstanceForAnyOf(t *testing.T) { + schema, err := Load("./testdata/instance-validate/test-schema-anyof.json") + require.NoError(t, err) + + // Valid values for both foo and bar + validInstance := map[string]any{ + "foo": "abc", + "bar": "abc", + } + assert.NoError(t, schema.validateAnyOf(validInstance)) + assert.NoError(t, schema.ValidateInstance(validInstance)) + + // Valid values for bar + validInstance = map[string]any{ + "foo": "abc", + "bar": "def", + } + assert.NoError(t, schema.validateAnyOf(validInstance)) + assert.NoError(t, schema.ValidateInstance(validInstance)) + + // Empty instance + emptyInstanceValue := map[string]any{} + assert.EqualError(t, schema.validateAnyOf(emptyInstanceValue), "instance does not match any of the schemas in anyOf") + assert.EqualError(t, schema.ValidateInstance(emptyInstanceValue), "instance does not match any of the schemas in anyOf") + + // Missing values for bar, invalid value for foo + missingInstanceValue := map[string]any{ + "foo": "xyz", + } + assert.EqualError(t, schema.validateAnyOf(missingInstanceValue), "instance does not match any of the schemas in anyOf") + assert.EqualError(t, schema.ValidateInstance(missingInstanceValue), "instance does not match any of the schemas in anyOf") + + // Valid value for bar, invalid value for foo + invalidInstanceValue := map[string]any{ + "foo": "xyz", + "bar": "abc", + } + assert.EqualError(t, schema.validateAnyOf(invalidInstanceValue), "instance does not match any of the schemas in anyOf") + assert.EqualError(t, schema.ValidateInstance(invalidInstanceValue), "instance does not match any of the schemas in anyOf") + + // Invalid value for both + invalidInstanceValue = map[string]any{ + "bar": "xyz", + } + assert.EqualError(t, schema.validateAnyOf(invalidInstanceValue), "instance does not match any of the schemas in anyOf") + assert.EqualError(t, schema.ValidateInstance(invalidInstanceValue), "instance does not match any of the schemas in anyOf") +} diff --git a/libs/jsonschema/testdata/instance-validate/test-schema-anyof.json b/libs/jsonschema/testdata/instance-validate/test-schema-anyof.json new file mode 100644 index 00000000..0f58b9ad --- /dev/null +++ b/libs/jsonschema/testdata/instance-validate/test-schema-anyof.json @@ -0,0 +1,24 @@ +{ + "anyOf": [ + { + "properties": { + "foo": { + "type": "string", + "const": "abc" + }, + "bar": { + "type": "string", + "const": "abc" + } + } + }, + { + "properties": { + "bar": { + "type": "string", + "const": "def" + } + } + } + ] +} diff --git a/libs/jsonschema/testdata/instance-validate/test-schema-const.json b/libs/jsonschema/testdata/instance-validate/test-schema-const.json new file mode 100644 index 00000000..3d609143 --- /dev/null +++ b/libs/jsonschema/testdata/instance-validate/test-schema-const.json @@ -0,0 +1,12 @@ +{ + "properties": { + "foo": { + "type": "string", + "const": "abc" + }, + "bar": { + "type": "string", + "const": "def" + } + } +} diff --git a/libs/template/config.go b/libs/template/config.go index bfb01d30..fff26453 100644 --- a/libs/template/config.go +++ b/libs/template/config.go @@ -125,35 +125,9 @@ func (c *config) skipPrompt(p jsonschema.Property, r *renderer) (bool, error) { return false, nil } - // Check if conditions specified by template author for skipping the prompt - // are satisfied. If they are not, we have to prompt for a user input. - if p.Schema.SkipPromptIf.Properties != nil { - for name, property := range p.Schema.SkipPromptIf.Properties { - if v, ok := c.values[name]; ok && v == property.Const { - continue - } - return false, nil - } - } - - // Check if AnyOf is set first. If so, then iterate over all property sets to find a match. - // If no match is found (allFalse stays true), we have to prompt for a user input. - if p.Schema.SkipPromptIf.AnyOf != nil { - allFalse := true - for _, properties := range p.Schema.SkipPromptIf.AnyOf { - match := true - for name, property := range properties.Properties { - if v, ok := c.values[name]; !ok || v != property.Const { - match = false - } - } - if match { - allFalse = false - } - } - if allFalse { - return false, nil - } + validationErr := p.Schema.SkipPromptIf.ValidateInstance(c.values) + if validationErr != nil { + return false, nil } if p.Schema.Default == nil {