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: This value does not have any attributes.`, }, { `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: \nwant: %s", test.WantError) } } }) } }