helper/config: clean up validation to work with nested items

/cc @pearkes
This commit is contained in:
Mitchell Hashimoto 2014-07-16 15:07:46 -07:00
parent 039d9635b4
commit e8eae17cc9
2 changed files with 94 additions and 39 deletions

View File

@ -94,14 +94,8 @@ func newValidatorKey(k string, req bool) (validatorKey, error) {
parts := strings.Split(k, ".") parts := strings.Split(k, ".")
if len(parts) > 1 && parts[1] == "*" { if len(parts) > 1 && parts[1] == "*" {
key := ""
if len(parts) >= 3 {
key = parts[2]
}
result = &nestedValidatorKey{ result = &nestedValidatorKey{
Prefix: parts[0], Parts: parts,
Key: key,
Required: req, Required: req,
} }
} else { } else {
@ -138,23 +132,43 @@ func (v *basicValidatorKey) Validate(
} }
type nestedValidatorKey struct { type nestedValidatorKey struct {
Prefix string Parts []string
Key string
Required bool Required bool
} }
func (v *nestedValidatorKey) Validate( func (v *nestedValidatorKey) validate(
m map[string]string) ([]string, []string, []error) { m map[string]string,
countStr, ok := m[v.Prefix+".#"] prefix string,
if !ok { offset int) ([]string, []string, []error) {
if !v.Required || v.Key != "" { if offset >= len(v.Parts) {
// Not present, that is okay // We're at the end. Look for a specific key.
return nil, nil, nil v2 := &basicValidatorKey{Key: prefix, Required: v.Required}
} else { return v2.Validate(m)
// Required and isn't present
return nil, nil, []error{fmt.Errorf(
"Key not found: %s", v.Prefix)}
} }
current := v.Parts[offset]
// If we're at offset 0, special case to start at the next one.
if offset == 0 {
return v.validate(m, current, offset+1)
}
// Determine if we're doing a "for all" or a specific key
if current != "*" {
// We're looking at a specific key, continue on.
return v.validate(m, prefix+"."+current, offset+1)
}
// We're doing a "for all", so we loop over.
countStr, ok := m[prefix+".#"]
if !ok {
if !v.Required {
// It wasn't required, so its no problem.
return nil, nil, nil
}
return nil, nil, []error{fmt.Errorf(
"Key not found: %s", prefix)}
} }
count, err := strconv.ParseInt(countStr, 0, 0) count, err := strconv.ParseInt(countStr, 0, 0)
@ -163,33 +177,38 @@ func (v *nestedValidatorKey) Validate(
panic("invalid flatmap array") panic("invalid flatmap array")
} }
var errs []error var e []error
used := make([]string, 1, count+1) var w []string
used[0] = v.Prefix + ".#" u := make([]string, 1, count+1)
u[0] = prefix + ".#"
for i := 0; i < int(count); i++ { for i := 0; i < int(count); i++ {
prefix := fmt.Sprintf("%s.%d.", v.Prefix, i) prefix := fmt.Sprintf("%s.%d", prefix, i)
if v.Key != "" { // Mark that we saw this specific key
key := prefix + v.Key u = append(u, prefix)
if _, ok := m[key]; !ok {
errs = append(errs, fmt.Errorf(
"%s[%d]: does not contain required key %s",
v.Prefix,
i,
v.Key))
}
}
// Mark all prefixes of this
for k, _ := range m { for k, _ := range m {
if k != prefix[:len(prefix)-1] { if !strings.HasPrefix(k, prefix+".") {
if !strings.HasPrefix(k, prefix) {
continue continue
} }
u = append(u, k)
} }
used = append(used, k) // If we have more parts, then validate deeper
if offset+1 < len(v.Parts) {
u2, w2, e2 := v.validate(m, prefix, offset+1)
u = append(u, u2...)
w = append(w, w2...)
e = append(e, e2...)
} }
} }
return used, nil, errs return u, w, e
}
func (v *nestedValidatorKey) Validate(
m map[string]string) ([]string, []string, []error) {
return v.validate(m, "", 0)
} }

View File

@ -95,6 +95,42 @@ func TestValidator_complex(t *testing.T) {
testInvalid(v, c) testInvalid(v, c)
} }
func TestValidator_complexNested(t *testing.T) {
v := &Validator{
Required: []string{
"ingress.*",
"ingress.*.from_port",
},
Optional: []string{
"ingress.*.cidr_blocks.*",
},
}
var c *terraform.ResourceConfig
// Valid
c = testConfig(t, map[string]interface{}{
"ingress": []map[string]interface{}{
map[string]interface{}{
"from_port": "80",
},
},
})
testValid(v, c)
// Valid
c = testConfig(t, map[string]interface{}{
"ingress": []map[string]interface{}{
map[string]interface{}{
"from_port": "80",
"cidr_blocks": []string{"foo"},
},
},
})
testValid(v, c)
}
func TestValidator_complexDeepRequired(t *testing.T) { func TestValidator_complexDeepRequired(t *testing.T) {
v := &Validator{ v := &Validator{
Required: []string{ Required: []string{
@ -118,7 +154,7 @@ func TestValidator_complexDeepRequired(t *testing.T) {
c = testConfig(t, map[string]interface{}{ c = testConfig(t, map[string]interface{}{
"foo": "bar", "foo": "bar",
}) })
testValid(v, c) testInvalid(v, c)
// Not a nested structure // Not a nested structure
c = testConfig(t, map[string]interface{}{ c = testConfig(t, map[string]interface{}{