command/format: Support list/map blocks with dynamic-typed attrs

Our null-to-empty normalization was previously assuming these would always
be collection types, but that isn't true when a block contains something
dynamic since we must then use tuple or object types instead to properly
represent all of the individual element types.
This commit is contained in:
Martin Atkins 2019-03-09 12:27:39 -08:00
parent 69772b11b1
commit dd1fa322a7
1 changed files with 47 additions and 12 deletions

View File

@ -299,12 +299,8 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
// For the sake of handling nested blocks, we'll treat a null list
// the same as an empty list since the config language doesn't
// distinguish these anyway.
if old.IsNull() {
old = cty.ListValEmpty(old.Type().ElementType())
}
if new.IsNull() {
new = cty.ListValEmpty(new.Type().ElementType())
}
old = ctyNullBlockListAsEmpty(old)
new = ctyNullBlockListAsEmpty(new)
oldItems := ctyCollectionValues(old)
newItems := ctyCollectionValues(new)
@ -358,12 +354,8 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
// For the sake of handling nested blocks, we'll treat a null set
// the same as an empty set since the config language doesn't
// distinguish these anyway.
if old.IsNull() {
old = cty.SetValEmpty(old.Type().ElementType())
}
if new.IsNull() {
new = cty.SetValEmpty(new.Type().ElementType())
}
old = ctyNullBlockSetAsEmpty(old)
new = ctyNullBlockSetAsEmpty(new)
oldItems := ctyCollectionValues(old)
newItems := ctyCollectionValues(new)
@ -1101,3 +1093,46 @@ func ctyEnsurePathCapacity(path cty.Path, minExtra int) cty.Path {
copy(newPath, path)
return newPath
}
// ctyNullBlockListAsEmpty either returns the given value verbatim if it is non-nil
// or returns an empty value of a suitable type to serve as a placeholder for it.
//
// In particular, this function handles the special situation where a "list" is
// actually represented as a tuple type where nested blocks contain
// dynamically-typed values.
func ctyNullBlockListAsEmpty(in cty.Value) cty.Value {
if !in.IsNull() {
return in
}
if ty := in.Type(); ty.IsListType() {
return cty.ListValEmpty(ty.ElementType())
}
return cty.EmptyTupleVal // must need a tuple, then
}
// ctyNullBlockMapAsEmpty either returns the given value verbatim if it is non-nil
// or returns an empty value of a suitable type to serve as a placeholder for it.
//
// In particular, this function handles the special situation where a "map" is
// actually represented as an object type where nested blocks contain
// dynamically-typed values.
func ctyNullBlockMapAsEmpty(in cty.Value) cty.Value {
if !in.IsNull() {
return in
}
if ty := in.Type(); ty.IsMapType() {
return cty.MapValEmpty(ty.ElementType())
}
return cty.EmptyObjectVal // must need an object, then
}
// ctyNullBlockSetAsEmpty either returns the given value verbatim if it is non-nil
// or returns an empty value of a suitable type to serve as a placeholder for it.
func ctyNullBlockSetAsEmpty(in cty.Value) cty.Value {
if !in.IsNull() {
return in
}
// Dynamically-typed attributes are not supported inside blocks backed by
// sets, so our result here is always a set.
return cty.SetValEmpty(in.Type().ElementType())
}