]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package configschema |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "regexp" | |
6 | ||
7 | "github.com/zclconf/go-cty/cty" | |
8 | ||
9 | multierror "github.com/hashicorp/go-multierror" | |
10 | ) | |
11 | ||
12 | var validName = regexp.MustCompile(`^[a-z0-9_]+$`) | |
13 | ||
14 | // InternalValidate returns an error if the receiving block and its child | |
15 | // schema definitions have any consistencies with the documented rules for | |
16 | // valid schema. | |
17 | // | |
18 | // This is intended to be used within unit tests to detect when a given | |
19 | // schema is invalid. | |
20 | func (b *Block) InternalValidate() error { | |
21 | if b == nil { | |
22 | return fmt.Errorf("top-level block schema is nil") | |
23 | } | |
24 | return b.internalValidate("", nil) | |
25 | ||
26 | } | |
27 | ||
28 | func (b *Block) internalValidate(prefix string, err error) error { | |
29 | for name, attrS := range b.Attributes { | |
30 | if attrS == nil { | |
31 | err = multierror.Append(err, fmt.Errorf("%s%s: attribute schema is nil", prefix, name)) | |
32 | continue | |
33 | } | |
34 | if !validName.MatchString(name) { | |
35 | err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name)) | |
36 | } | |
37 | if attrS.Optional == false && attrS.Required == false && attrS.Computed == false { | |
38 | err = multierror.Append(err, fmt.Errorf("%s%s: must set Optional, Required or Computed", prefix, name)) | |
39 | } | |
40 | if attrS.Optional && attrS.Required { | |
41 | err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Optional and Required", prefix, name)) | |
42 | } | |
43 | if attrS.Computed && attrS.Required { | |
44 | err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Computed and Required", prefix, name)) | |
45 | } | |
46 | if attrS.Type == cty.NilType { | |
47 | err = multierror.Append(err, fmt.Errorf("%s%s: Type must be set to something other than cty.NilType", prefix, name)) | |
48 | } | |
49 | } | |
50 | ||
51 | for name, blockS := range b.BlockTypes { | |
52 | if blockS == nil { | |
53 | err = multierror.Append(err, fmt.Errorf("%s%s: block schema is nil", prefix, name)) | |
54 | continue | |
55 | } | |
56 | ||
57 | if _, isAttr := b.Attributes[name]; isAttr { | |
58 | err = multierror.Append(err, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name)) | |
59 | } else if !validName.MatchString(name) { | |
60 | err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name)) | |
61 | } | |
62 | ||
63 | if blockS.MinItems < 0 || blockS.MaxItems < 0 { | |
64 | err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name)) | |
65 | } | |
66 | ||
67 | switch blockS.Nesting { | |
68 | case NestingSingle: | |
69 | switch { | |
70 | case blockS.MinItems != blockS.MaxItems: | |
71 | err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name)) | |
72 | case blockS.MinItems < 0 || blockS.MinItems > 1: | |
73 | err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name)) | |
74 | } | |
107c1cdb ND |
75 | case NestingGroup: |
76 | if blockS.MinItems != 0 || blockS.MaxItems != 0 { | |
77 | err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems cannot be used in NestingGroup mode", prefix, name)) | |
78 | } | |
15c0b25d AP |
79 | case NestingList, NestingSet: |
80 | if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 { | |
81 | err = multierror.Append(err, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, blockS.Nesting)) | |
82 | } | |
107c1cdb ND |
83 | if blockS.Nesting == NestingSet { |
84 | ety := blockS.Block.ImpliedType() | |
85 | if ety.HasDynamicTypes() { | |
86 | // This is not permitted because the HCL (cty) set implementation | |
87 | // needs to know the exact type of set elements in order to | |
88 | // properly hash them, and so can't support mixed types. | |
89 | err = multierror.Append(err, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name)) | |
90 | } | |
91 | } | |
15c0b25d AP |
92 | case NestingMap: |
93 | if blockS.MinItems != 0 || blockS.MaxItems != 0 { | |
94 | err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name)) | |
95 | } | |
96 | default: | |
97 | err = multierror.Append(err, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting)) | |
98 | } | |
99 | ||
100 | subPrefix := prefix + name + "." | |
101 | err = blockS.Block.internalValidate(subPrefix, err) | |
102 | } | |
103 | ||
104 | return err | |
105 | } |