configschema: Fix ConfigSchema bugs with nested blocks
We were iterating over the wrong value to recursively coerce content for nested blocks, and also incorrectly constructing the cty.Path used in errors.
This commit is contained in:
parent
0120d53baf
commit
d8bf3cc4e0
|
@ -108,7 +108,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !coll.CanIterateElements() {
|
if !coll.CanIterateElements() {
|
||||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q must be a list", typeName)
|
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
|
||||||
}
|
}
|
||||||
l := coll.LengthInt()
|
l := coll.LengthInt()
|
||||||
if l < blockS.MinItems {
|
if l < blockS.MinItems {
|
||||||
|
@ -122,15 +122,18 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
elems := make([]cty.Value, 0, l)
|
elems := make([]cty.Value, 0, l)
|
||||||
for it := in.ElementIterator(); it.Next(); {
|
{
|
||||||
|
path = append(path, cty.GetAttrStep{Name: typeName})
|
||||||
|
for it := coll.ElementIterator(); it.Next(); {
|
||||||
var err error
|
var err error
|
||||||
_, val := it.Element()
|
idx, val := it.Element()
|
||||||
val, err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
|
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.UnknownVal(b.ImpliedType()), err
|
return cty.UnknownVal(b.ImpliedType()), err
|
||||||
}
|
}
|
||||||
elems = append(elems, val)
|
elems = append(elems, val)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
attrs[typeName] = cty.ListVal(elems)
|
attrs[typeName] = cty.ListVal(elems)
|
||||||
case blockS.MinItems == 0:
|
case blockS.MinItems == 0:
|
||||||
attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
|
attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
|
||||||
|
@ -153,7 +156,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !coll.CanIterateElements() {
|
if !coll.CanIterateElements() {
|
||||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q must be a set", typeName)
|
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
|
||||||
}
|
}
|
||||||
l := coll.LengthInt()
|
l := coll.LengthInt()
|
||||||
if l < blockS.MinItems {
|
if l < blockS.MinItems {
|
||||||
|
@ -167,15 +170,18 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
elems := make([]cty.Value, 0, l)
|
elems := make([]cty.Value, 0, l)
|
||||||
for it := in.ElementIterator(); it.Next(); {
|
{
|
||||||
|
path = append(path, cty.GetAttrStep{Name: typeName})
|
||||||
|
for it := coll.ElementIterator(); it.Next(); {
|
||||||
var err error
|
var err error
|
||||||
_, val := it.Element()
|
idx, val := it.Element()
|
||||||
val, err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
|
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.UnknownVal(b.ImpliedType()), err
|
return cty.UnknownVal(b.ImpliedType()), err
|
||||||
}
|
}
|
||||||
elems = append(elems, val)
|
elems = append(elems, val)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
attrs[typeName] = cty.SetVal(elems)
|
attrs[typeName] = cty.SetVal(elems)
|
||||||
case blockS.MinItems == 0:
|
case blockS.MinItems == 0:
|
||||||
attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
|
attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
|
||||||
|
@ -198,7 +204,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !coll.CanIterateElements() {
|
if !coll.CanIterateElements() {
|
||||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q must be a map", typeName)
|
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
|
||||||
}
|
}
|
||||||
l := coll.LengthInt()
|
l := coll.LengthInt()
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
|
@ -206,18 +212,21 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
elems := make(map[string]cty.Value)
|
elems := make(map[string]cty.Value)
|
||||||
for it := in.ElementIterator(); it.Next(); {
|
{
|
||||||
|
path = append(path, cty.GetAttrStep{Name: typeName})
|
||||||
|
for it := coll.ElementIterator(); it.Next(); {
|
||||||
var err error
|
var err error
|
||||||
key, val := it.Element()
|
key, val := it.Element()
|
||||||
if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
|
if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
|
||||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q must be a map", typeName)
|
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
|
||||||
}
|
}
|
||||||
val, err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
|
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cty.UnknownVal(b.ImpliedType()), err
|
return cty.UnknownVal(b.ImpliedType()), err
|
||||||
}
|
}
|
||||||
elems[key.AsString()] = val
|
elems[key.AsString()] = val
|
||||||
}
|
}
|
||||||
|
}
|
||||||
attrs[typeName] = cty.MapVal(elems)
|
attrs[typeName] = cty.MapVal(elems)
|
||||||
default:
|
default:
|
||||||
attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
|
attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCoerceValue(t *testing.T) {
|
func TestCoerceValue(t *testing.T) {
|
||||||
|
@ -66,7 +68,125 @@ func TestCoerceValue(t *testing.T) {
|
||||||
"foo": cty.True,
|
"foo": cty.True,
|
||||||
}),
|
}),
|
||||||
cty.DynamicVal,
|
cty.DynamicVal,
|
||||||
`an object is required`,
|
`.foo: an object is required`,
|
||||||
|
},
|
||||||
|
"list block with one item": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Block: Block{},
|
||||||
|
Nesting: NestingList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}),
|
||||||
|
}),
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
"set block with one item": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Block: Block{},
|
||||||
|
Nesting: NestingSet,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}), // can implicitly convert to set
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.SetVal([]cty.Value{cty.EmptyObjectVal}),
|
||||||
|
}),
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
"map block with one item": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Block: Block{},
|
||||||
|
Nesting: NestingMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}),
|
||||||
|
}),
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
"list block with one item having an attribute": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Block: Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"bar": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Nesting: NestingList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("hello"),
|
||||||
|
})}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("hello"),
|
||||||
|
})}),
|
||||||
|
}),
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
"list block with one item having a missing attribute": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Block: Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"bar": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Nesting: NestingList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.EmptyObjectVal}),
|
||||||
|
}),
|
||||||
|
cty.DynamicVal,
|
||||||
|
`.foo[0]: attribute "bar" is required`,
|
||||||
|
},
|
||||||
|
"list block with one item having an extraneous attribute": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Block: Block{},
|
||||||
|
Nesting: NestingList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("hello"),
|
||||||
|
})}),
|
||||||
|
}),
|
||||||
|
cty.DynamicVal,
|
||||||
|
`.foo[0]: unexpected attribute "bar"`,
|
||||||
},
|
},
|
||||||
"missing optional attribute": {
|
"missing optional attribute": {
|
||||||
&Block{
|
&Block{
|
||||||
|
@ -207,6 +327,21 @@ func TestCoerceValue(t *testing.T) {
|
||||||
cty.DynamicVal,
|
cty.DynamicVal,
|
||||||
`unexpected attribute "foo"`,
|
`unexpected attribute "foo"`,
|
||||||
},
|
},
|
||||||
|
"wrong attribute type": {
|
||||||
|
&Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"foo": {
|
||||||
|
Type: cty.Number,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.False,
|
||||||
|
}),
|
||||||
|
cty.DynamicVal,
|
||||||
|
`.foo: number required`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
|
@ -218,7 +353,7 @@ func TestCoerceValue(t *testing.T) {
|
||||||
t.Fatalf("coersion succeeded; want error: %q", test.WantErr)
|
t.Fatalf("coersion succeeded; want error: %q", test.WantErr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gotErr := gotErrObj.Error()
|
gotErr := tfdiags.FormatError(gotErrObj)
|
||||||
if gotErr != test.WantErr {
|
if gotErr != test.WantErr {
|
||||||
t.Fatalf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr)
|
t.Fatalf("wrong error\ngot: %s\nwant: %s", gotErr, test.WantErr)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue