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) {
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue