Merge pull request #29580 from hashicorp/jbardin/proposed-new-empty-containers
handle empty containers in ProposedNew NestedTypes
This commit is contained in:
commit
2afa0a5e75
|
@ -27,16 +27,19 @@ func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
|
|||
}
|
||||
|
||||
func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||
convType := b.specType()
|
||||
impliedType := convType.WithoutOptionalAttributesDeep()
|
||||
|
||||
switch {
|
||||
case in.IsNull():
|
||||
return cty.NullVal(b.ImpliedType()), nil
|
||||
return cty.NullVal(impliedType), nil
|
||||
case !in.IsKnown():
|
||||
return cty.UnknownVal(b.ImpliedType()), nil
|
||||
return cty.UnknownVal(impliedType), nil
|
||||
}
|
||||
|
||||
ty := in.Type()
|
||||
if !ty.IsObjectType() {
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("an object is required")
|
||||
}
|
||||
|
||||
for name := range ty.AttributeTypes() {
|
||||
|
@ -46,29 +49,32 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
if _, defined := b.BlockTypes[name]; defined {
|
||||
continue
|
||||
}
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("unexpected attribute %q", name)
|
||||
}
|
||||
|
||||
attrs := make(map[string]cty.Value)
|
||||
|
||||
for name, attrS := range b.Attributes {
|
||||
attrType := impliedType.AttributeType(name)
|
||||
attrConvType := convType.AttributeType(name)
|
||||
|
||||
var val cty.Value
|
||||
switch {
|
||||
case ty.HasAttribute(name):
|
||||
val = in.GetAttr(name)
|
||||
case attrS.Computed || attrS.Optional:
|
||||
val = cty.NullVal(attrS.Type)
|
||||
val = cty.NullVal(attrType)
|
||||
default:
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("attribute %q is required", name)
|
||||
}
|
||||
|
||||
val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
|
||||
val, err := convert.Convert(val, attrConvType)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(b.ImpliedType()), err
|
||||
return cty.UnknownVal(impliedType), append(path, cty.GetAttrStep{Name: name}).NewError(err)
|
||||
}
|
||||
|
||||
attrs[name] = val
|
||||
}
|
||||
|
||||
for typeName, blockS := range b.BlockTypes {
|
||||
switch blockS.Nesting {
|
||||
|
||||
|
@ -79,7 +85,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
val := in.GetAttr(typeName)
|
||||
attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
|
||||
if err != nil {
|
||||
return cty.UnknownVal(b.ImpliedType()), err
|
||||
return cty.UnknownVal(impliedType), err
|
||||
}
|
||||
default:
|
||||
attrs[typeName] = blockS.EmptyValue()
|
||||
|
@ -100,7 +106,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
}
|
||||
|
||||
if !coll.CanIterateElements() {
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("must be a list")
|
||||
}
|
||||
l := coll.LengthInt()
|
||||
|
||||
|
@ -116,7 +122,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
idx, val := it.Element()
|
||||
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
|
||||
if err != nil {
|
||||
return cty.UnknownVal(b.ImpliedType()), err
|
||||
return cty.UnknownVal(impliedType), err
|
||||
}
|
||||
elems = append(elems, val)
|
||||
}
|
||||
|
@ -141,7 +147,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
}
|
||||
|
||||
if !coll.CanIterateElements() {
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("must be a set")
|
||||
}
|
||||
l := coll.LengthInt()
|
||||
|
||||
|
@ -157,7 +163,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
idx, val := it.Element()
|
||||
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
|
||||
if err != nil {
|
||||
return cty.UnknownVal(b.ImpliedType()), err
|
||||
return cty.UnknownVal(impliedType), err
|
||||
}
|
||||
elems = append(elems, val)
|
||||
}
|
||||
|
@ -182,7 +188,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
}
|
||||
|
||||
if !coll.CanIterateElements() {
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
|
||||
}
|
||||
l := coll.LengthInt()
|
||||
if l == 0 {
|
||||
|
@ -196,11 +202,11 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
var err error
|
||||
key, val := it.Element()
|
||||
if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
|
||||
return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
|
||||
return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
|
||||
}
|
||||
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
|
||||
if err != nil {
|
||||
return cty.UnknownVal(b.ImpliedType()), err
|
||||
return cty.UnknownVal(impliedType), err
|
||||
}
|
||||
elems[key.AsString()] = val
|
||||
}
|
||||
|
@ -240,11 +246,3 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
|||
|
||||
return cty.ObjectVal(attrs), nil
|
||||
}
|
||||
|
||||
func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
|
||||
val, err := convert.Convert(in, a.Type)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(a.Type), path.NewError(err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
|
|
@ -538,6 +538,67 @@ func TestCoerceValue(t *testing.T) {
|
|||
}),
|
||||
``,
|
||||
},
|
||||
"nested types": {
|
||||
// handle NestedTypes
|
||||
&Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"foo": {
|
||||
NestedType: &Object{
|
||||
Nesting: NestingList,
|
||||
Attributes: map[string]*Attribute{
|
||||
"bar": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"baz": {
|
||||
Type: cty.Map(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
"fob": {
|
||||
NestedType: &Object{
|
||||
Nesting: NestingSet,
|
||||
Attributes: map[string]*Attribute{
|
||||
"bar": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("beep"),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("boop"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("beep"),
|
||||
"baz": cty.NullVal(cty.Map(cty.String)),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("boop"),
|
||||
"baz": cty.NullVal(cty.Map(cty.String)),
|
||||
}),
|
||||
}),
|
||||
"fob": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
|
||||
"bar": cty.String,
|
||||
}))),
|
||||
}),
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
|
|
|
@ -308,7 +308,9 @@ func proposedNewAttributes(attrs map[string]*configschema.Attribute, prior, conf
|
|||
}
|
||||
|
||||
func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value) cty.Value {
|
||||
var newV cty.Value
|
||||
// If the config is null or empty, we will be using this default value.
|
||||
newV := config
|
||||
|
||||
switch schema.Nesting {
|
||||
case configschema.NestingSingle:
|
||||
if !config.IsNull() {
|
||||
|
@ -323,6 +325,7 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
|||
if config.IsKnown() && !config.IsNull() {
|
||||
configVLen = config.LengthInt()
|
||||
}
|
||||
|
||||
if configVLen > 0 {
|
||||
newVals := make([]cty.Value, 0, configVLen)
|
||||
for it := config.ElementIterator(); it.Next(); {
|
||||
|
@ -345,8 +348,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
|||
} else {
|
||||
newV = cty.ListVal(newVals)
|
||||
}
|
||||
} else {
|
||||
newV = cty.NullVal(schema.ImpliedType())
|
||||
}
|
||||
|
||||
case configschema.NestingMap:
|
||||
|
@ -378,8 +379,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
|||
// object values so that elements might have different types
|
||||
// in case of dynamically-typed attributes.
|
||||
newV = cty.ObjectVal(newVals)
|
||||
} else {
|
||||
newV = cty.NullVal(schema.ImpliedType())
|
||||
}
|
||||
} else {
|
||||
configVLen := 0
|
||||
|
@ -403,8 +402,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
|||
newVals[k] = cty.ObjectVal(newEV)
|
||||
}
|
||||
newV = cty.MapVal(newVals)
|
||||
} else {
|
||||
newV = cty.NullVal(schema.ImpliedType())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,8 +443,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
|
|||
}
|
||||
}
|
||||
newV = cty.SetVal(newVals)
|
||||
} else {
|
||||
newV = cty.NullVal(schema.ImpliedType())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1461,6 +1461,81 @@ func TestProposedNew(t *testing.T) {
|
|||
}))),
|
||||
}),
|
||||
},
|
||||
"expected empty NestedTypes": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"set": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSet,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {Type: cty.String},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
"map": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingMap,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {Type: cty.String},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
|
||||
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
|
||||
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
|
||||
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
|
||||
}),
|
||||
},
|
||||
"optional types set replacement": {
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"set": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSet,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"set": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("old"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"set": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("new"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"set": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("new"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
|
|
Loading…
Reference in New Issue