Add anyOf props as required and error when required fields are not specified

This commit is contained in:
Shreyas Goenka 2024-01-24 12:10:55 +05:30
parent 769d092108
commit fca4729221
3 changed files with 44 additions and 15 deletions

View File

@ -73,12 +73,20 @@ func (s *Schema) validateAdditionalProperties(instance map[string]any) error {
return nil return nil
} }
type RequiredPropertyMissingError struct {
Name string
}
func (err RequiredPropertyMissingError) Error() string {
return fmt.Sprintf("no value provided for required property %s", err.Name)
}
// This function validates that all require properties in the schema have values // This function validates that all require properties in the schema have values
// in the instance. // in the instance.
func (s *Schema) validateRequired(instance map[string]any) error { func (s *Schema) validateRequired(instance map[string]any) error {
for _, name := range s.Required { for _, name := range s.Required {
if _, ok := instance[name]; !ok { if _, ok := instance[name]; !ok {
return fmt.Errorf("no value provided for required property %s", name) return RequiredPropertyMissingError{Name: name}
} }
} }
return nil return nil

View File

@ -127,13 +127,27 @@ func (c *config) skipPrompt(p jsonschema.Property, r *renderer) (bool, error) {
// All fields referred to in a SkipPromptIf condition are implicitly made required and // All fields referred to in a SkipPromptIf condition are implicitly made required and
// we diverge from strictly following the JSON schema because it makes the author UX better. // we diverge from strictly following the JSON schema because it makes the author UX better.
var keys []string required := make(map[string]struct{})
for k := range p.Schema.SkipPromptIf.Properties { for _, k := range p.Schema.SkipPromptIf.Required {
keys = append(keys, k) required[k] = struct{}{}
} }
p.Schema.SkipPromptIf.Required = append(keys, p.Schema.SkipPromptIf.Required...) for k := range p.Schema.SkipPromptIf.Properties {
required[k] = struct{}{}
}
for _, schema := range p.Schema.SkipPromptIf.AnyOf {
for k := range schema.Properties {
required[k] = struct{}{}
}
}
p.Schema.SkipPromptIf.Required = maps.Keys(required)
// Validate the partial config against skip_prompt_if schema
validationErr := p.Schema.SkipPromptIf.ValidateInstance(c.values) validationErr := p.Schema.SkipPromptIf.ValidateInstance(c.values)
target := jsonschema.RequiredPropertyMissingError{}
if errors.As(validationErr, &target) {
return false, fmt.Errorf("property %s is used in skip_prompt_if but has no value assigned", target.Name)
}
if validationErr != nil { if validationErr != nil {
return false, nil return false, nil
} }

View File

@ -451,8 +451,10 @@ func TestPromptIsSkippedAnyOf(t *testing.T) {
assert.False(t, skip) assert.False(t, skip)
// Values do not match skip condition. Prompt should not be skipped. // Values do not match skip condition. Prompt should not be skipped.
c.values["abc"] = "foobar" c.values = map[string]any{
c.values["def"] = 1234 "abc": "foobar",
"def": 1234,
}
skip, err = c.skipPrompt(jsonschema.Property{ skip, err = c.skipPrompt(jsonschema.Property{
Name: "xyz", Name: "xyz",
Schema: c.schema.Properties["xyz"], Schema: c.schema.Properties["xyz"],
@ -462,19 +464,21 @@ func TestPromptIsSkippedAnyOf(t *testing.T) {
assert.NotContains(t, c.values, "xyz") assert.NotContains(t, c.values, "xyz")
// Missing values. Prompt should not be skipped. // Missing values. Prompt should not be skipped.
c.values["abc"] = "foobar" c.values = map[string]any{
skip, err = c.skipPrompt(jsonschema.Property{ "abc": "foobar",
}
_, err = c.skipPrompt(jsonschema.Property{
Name: "xyz", Name: "xyz",
Schema: c.schema.Properties["xyz"], Schema: c.schema.Properties["xyz"],
}, testRenderer()) }, testRenderer())
assert.NoError(t, err) assert.ErrorContains(t, err, "property def is used in skip_prompt_if but has no value assigned")
assert.False(t, skip)
assert.NotContains(t, c.values, "xyz")
// Values match skip condition. Prompt should be skipped. Default value should // Values match skip condition. Prompt should be skipped. Default value should
// be assigned to "xyz". // be assigned to "xyz".
c.values["abc"] = "foobar" c.values = map[string]any{
c.values["def"] = 123 "abc": "foobar",
"def": 123,
}
skip, err = c.skipPrompt(jsonschema.Property{ skip, err = c.skipPrompt(jsonschema.Property{
Name: "xyz", Name: "xyz",
Schema: c.schema.Properties["xyz"], Schema: c.schema.Properties["xyz"],
@ -485,7 +489,10 @@ func TestPromptIsSkippedAnyOf(t *testing.T) {
// Values match skip condition. Prompt should be skipped. Default value should // Values match skip condition. Prompt should be skipped. Default value should
// be assigned to "xyz". // be assigned to "xyz".
c.values["abc"] = "barfoo" c.values = map[string]any{
"abc": "barfoo",
"def": 0,
}
skip, err = c.skipPrompt(jsonschema.Property{ skip, err = c.skipPrompt(jsonschema.Property{
Name: "xyz", Name: "xyz",
Schema: c.schema.Properties["xyz"], Schema: c.schema.Properties["xyz"],