| package configschema |
| |
| import ( |
| "testing" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/hashicorp/hcl/v2/hclsyntax" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| func TestStaticValidateTraversal(t *testing.T) { |
| attrs := map[string]*Attribute{ |
| "str": {Type: cty.String, Optional: true}, |
| "list": {Type: cty.List(cty.String), Optional: true}, |
| "dyn": {Type: cty.DynamicPseudoType, Optional: true}, |
| } |
| schema := &Block{ |
| Attributes: attrs, |
| BlockTypes: map[string]*NestedBlock{ |
| "single_block": { |
| Nesting: NestingSingle, |
| Block: Block{ |
| Attributes: attrs, |
| }, |
| }, |
| "list_block": { |
| Nesting: NestingList, |
| Block: Block{ |
| Attributes: attrs, |
| }, |
| }, |
| "set_block": { |
| Nesting: NestingSet, |
| Block: Block{ |
| Attributes: attrs, |
| }, |
| }, |
| "map_block": { |
| Nesting: NestingMap, |
| Block: Block{ |
| Attributes: attrs, |
| }, |
| }, |
| }, |
| } |
| |
| tests := []struct { |
| Traversal string |
| WantError string |
| }{ |
| { |
| `obj`, |
| ``, |
| }, |
| { |
| `obj.str`, |
| ``, |
| }, |
| { |
| `obj.str.nonexist`, |
| `Unsupported attribute: Can't access attributes on a primitive-typed value (string).`, |
| }, |
| { |
| `obj.list`, |
| ``, |
| }, |
| { |
| `obj.list[0]`, |
| ``, |
| }, |
| { |
| `obj.list.nonexist`, |
| `Unsupported attribute: This value does not have any attributes.`, |
| }, |
| { |
| `obj.dyn`, |
| ``, |
| }, |
| { |
| `obj.dyn.anything_goes`, |
| ``, |
| }, |
| { |
| `obj.dyn[0]`, |
| ``, |
| }, |
| { |
| `obj.nonexist`, |
| `Unsupported attribute: This object has no argument, nested block, or exported attribute named "nonexist".`, |
| }, |
| { |
| `obj[1]`, |
| `Invalid index operation: Only attribute access is allowed here, using the dot operator.`, |
| }, |
| { |
| `obj["str"]`, // we require attribute access for the first step to avoid ambiguity with resource instance indices |
| `Invalid index operation: Only attribute access is allowed here. Did you mean to access attribute "str" using the dot operator?`, |
| }, |
| { |
| `obj.atr`, |
| `Unsupported attribute: This object has no argument, nested block, or exported attribute named "atr". Did you mean "str"?`, |
| }, |
| { |
| `obj.single_block`, |
| ``, |
| }, |
| { |
| `obj.single_block.str`, |
| ``, |
| }, |
| { |
| `obj.single_block.nonexist`, |
| `Unsupported attribute: This object has no argument, nested block, or exported attribute named "nonexist".`, |
| }, |
| { |
| `obj.list_block`, |
| ``, |
| }, |
| { |
| `obj.list_block[0]`, |
| ``, |
| }, |
| { |
| `obj.list_block[0].str`, |
| ``, |
| }, |
| { |
| `obj.list_block[0].nonexist`, |
| `Unsupported attribute: This object has no argument, nested block, or exported attribute named "nonexist".`, |
| }, |
| { |
| `obj.list_block.str`, |
| `Invalid operation: Block type "list_block" is represented by a list of objects, so it must be indexed using a numeric key, like .list_block[0].`, |
| }, |
| { |
| `obj.set_block`, |
| ``, |
| }, |
| { |
| `obj.set_block[0]`, |
| `Cannot index a set value: Block type "set_block" is represented by a set of objects, and set elements do not have addressable keys. To find elements matching specific criteria, use a "for" expression with an "if" clause.`, |
| }, |
| { |
| `obj.set_block.str`, |
| `Cannot index a set value: Block type "set_block" is represented by a set of objects, and set elements do not have addressable keys. To find elements matching specific criteria, use a "for" expression with an "if" clause.`, |
| }, |
| { |
| `obj.map_block`, |
| ``, |
| }, |
| { |
| `obj.map_block.anything`, |
| ``, |
| }, |
| { |
| `obj.map_block["anything"]`, |
| ``, |
| }, |
| { |
| `obj.map_block.anything.str`, |
| ``, |
| }, |
| { |
| `obj.map_block["anything"].str`, |
| ``, |
| }, |
| { |
| `obj.map_block.anything.nonexist`, |
| `Unsupported attribute: This object has no argument, nested block, or exported attribute named "nonexist".`, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.Traversal, func(t *testing.T) { |
| traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(test.Traversal), "", hcl.Pos{Line: 1, Column: 1}) |
| for _, diag := range parseDiags { |
| t.Error(diag.Error()) |
| } |
| |
| // We trim the "obj." portion from the front since StaticValidateTraversal |
| // only works with relative traversals. |
| traversal = traversal[1:] |
| |
| diags := schema.StaticValidateTraversal(traversal) |
| if test.WantError == "" { |
| if diags.HasErrors() { |
| t.Errorf("unexpected error: %s", diags.Err().Error()) |
| } |
| } else { |
| if diags.HasErrors() { |
| if got := diags.Err().Error(); got != test.WantError { |
| t.Errorf("wrong error\ngot: %s\nwant: %s", got, test.WantError) |
| } |
| } else { |
| t.Errorf("wrong error\ngot: <no error>\nwant: %s", test.WantError) |
| } |
| } |
| }) |
| } |
| } |