Merge pull request #29580 from hashicorp/jbardin/proposed-new-empty-containers

handle empty containers in ProposedNew NestedTypes
This commit is contained in:
James Bardin 2021-09-15 09:06:29 -04:00 committed by GitHub
commit 2afa0a5e75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 34 deletions

View File

@ -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) { func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
convType := b.specType()
impliedType := convType.WithoutOptionalAttributesDeep()
switch { switch {
case in.IsNull(): case in.IsNull():
return cty.NullVal(b.ImpliedType()), nil return cty.NullVal(impliedType), nil
case !in.IsKnown(): case !in.IsKnown():
return cty.UnknownVal(b.ImpliedType()), nil return cty.UnknownVal(impliedType), nil
} }
ty := in.Type() ty := in.Type()
if !ty.IsObjectType() { 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() { 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 { if _, defined := b.BlockTypes[name]; defined {
continue 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) attrs := make(map[string]cty.Value)
for name, attrS := range b.Attributes { for name, attrS := range b.Attributes {
attrType := impliedType.AttributeType(name)
attrConvType := convType.AttributeType(name)
var val cty.Value var val cty.Value
switch { switch {
case ty.HasAttribute(name): case ty.HasAttribute(name):
val = in.GetAttr(name) val = in.GetAttr(name)
case attrS.Computed || attrS.Optional: case attrS.Computed || attrS.Optional:
val = cty.NullVal(attrS.Type) val = cty.NullVal(attrType)
default: 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 { if err != nil {
return cty.UnknownVal(b.ImpliedType()), err return cty.UnknownVal(impliedType), append(path, cty.GetAttrStep{Name: name}).NewError(err)
} }
attrs[name] = val attrs[name] = val
} }
for typeName, blockS := range b.BlockTypes { for typeName, blockS := range b.BlockTypes {
switch blockS.Nesting { switch blockS.Nesting {
@ -79,7 +85,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
val := in.GetAttr(typeName) val := in.GetAttr(typeName)
attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName})) attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
if err != nil { if err != nil {
return cty.UnknownVal(b.ImpliedType()), err return cty.UnknownVal(impliedType), err
} }
default: default:
attrs[typeName] = blockS.EmptyValue() attrs[typeName] = blockS.EmptyValue()
@ -100,7 +106,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("must be a list") return cty.UnknownVal(impliedType), path.NewErrorf("must be a list")
} }
l := coll.LengthInt() l := coll.LengthInt()
@ -116,7 +122,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
idx, val := it.Element() idx, val := it.Element()
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx})) 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(impliedType), err
} }
elems = append(elems, val) elems = append(elems, val)
} }
@ -141,7 +147,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("must be a set") return cty.UnknownVal(impliedType), path.NewErrorf("must be a set")
} }
l := coll.LengthInt() l := coll.LengthInt()
@ -157,7 +163,7 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
idx, val := it.Element() idx, val := it.Element()
val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx})) 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(impliedType), err
} }
elems = append(elems, val) elems = append(elems, val)
} }
@ -182,7 +188,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("must be a map") return cty.UnknownVal(impliedType), path.NewErrorf("must be a map")
} }
l := coll.LengthInt() l := coll.LengthInt()
if l == 0 { if l == 0 {
@ -196,11 +202,11 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
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("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})) 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(impliedType), err
} }
elems[key.AsString()] = val 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 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
}

View File

@ -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 { for name, test := range tests {

View File

@ -308,7 +308,9 @@ func proposedNewAttributes(attrs map[string]*configschema.Attribute, prior, conf
} }
func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value) cty.Value { 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 { switch schema.Nesting {
case configschema.NestingSingle: case configschema.NestingSingle:
if !config.IsNull() { if !config.IsNull() {
@ -323,6 +325,7 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
if config.IsKnown() && !config.IsNull() { if config.IsKnown() && !config.IsNull() {
configVLen = config.LengthInt() configVLen = config.LengthInt()
} }
if configVLen > 0 { if configVLen > 0 {
newVals := make([]cty.Value, 0, configVLen) newVals := make([]cty.Value, 0, configVLen)
for it := config.ElementIterator(); it.Next(); { for it := config.ElementIterator(); it.Next(); {
@ -345,8 +348,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
} else { } else {
newV = cty.ListVal(newVals) newV = cty.ListVal(newVals)
} }
} else {
newV = cty.NullVal(schema.ImpliedType())
} }
case configschema.NestingMap: 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 // object values so that elements might have different types
// in case of dynamically-typed attributes. // in case of dynamically-typed attributes.
newV = cty.ObjectVal(newVals) newV = cty.ObjectVal(newVals)
} else {
newV = cty.NullVal(schema.ImpliedType())
} }
} else { } else {
configVLen := 0 configVLen := 0
@ -403,8 +402,6 @@ func proposedNewNestedType(schema *configschema.Object, prior, config cty.Value)
newVals[k] = cty.ObjectVal(newEV) newVals[k] = cty.ObjectVal(newEV)
} }
newV = cty.MapVal(newVals) 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) newV = cty.SetVal(newVals)
} else {
newV = cty.NullVal(schema.ImpliedType())
} }
} }

View File

@ -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 { for name, test := range tests {