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, ".")
if len(parts) > 1 && parts[1] == "*" {
key := ""
if len(parts) >= 3 {
key = parts[2]
}
result = &nestedValidatorKey{
Prefix: parts[0],
Key: key,
Parts: parts,
Required: req,
}
} else {
@ -138,23 +132,43 @@ func (v *basicValidatorKey) Validate(
}
type nestedValidatorKey struct {
Prefix string
Key string
Parts []string
Required bool
}
func (v *nestedValidatorKey) Validate(
m map[string]string) ([]string, []string, []error) {
countStr, ok := m[v.Prefix+".#"]
if !ok {
if !v.Required || v.Key != "" {
// Not present, that is okay
return nil, nil, nil
} else {
// Required and isn't present
return nil, nil, []error{fmt.Errorf(
"Key not found: %s", v.Prefix)}
func (v *nestedValidatorKey) validate(
m map[string]string,
prefix string,
offset int) ([]string, []string, []error) {
if offset >= len(v.Parts) {
// We're at the end. Look for a specific key.
v2 := &basicValidatorKey{Key: prefix, Required: v.Required}
return v2.Validate(m)
}
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)
@ -163,33 +177,38 @@ func (v *nestedValidatorKey) Validate(
panic("invalid flatmap array")
}
var errs []error
used := make([]string, 1, count+1)
used[0] = v.Prefix + ".#"
var e []error
var w []string
u := make([]string, 1, count+1)
u[0] = prefix + ".#"
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 != "" {
key := prefix + v.Key
if _, ok := m[key]; !ok {
errs = append(errs, fmt.Errorf(
"%s[%d]: does not contain required key %s",
v.Prefix,
i,
v.Key))
}
}
// Mark that we saw this specific key
u = append(u, prefix)
// Mark all prefixes of this
for k, _ := range m {
if k != prefix[:len(prefix)-1] {
if !strings.HasPrefix(k, prefix) {
if !strings.HasPrefix(k, prefix+".") {
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)
}
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) {
v := &Validator{
Required: []string{
@ -118,7 +154,7 @@ func TestValidator_complexDeepRequired(t *testing.T) {
c = testConfig(t, map[string]interface{}{
"foo": "bar",
})
testValid(v, c)
testInvalid(v, c)
// Not a nested structure
c = testConfig(t, map[string]interface{}{