switch blocks based on value type, and check attrs
Check attributes on null objects, and fill in unknowns. If we're evaluating the object, it either means we are at the top level, or a NestingSingle block was present, and in either case we need to treat the attributes as null rather than the entire object. Switch on the block types rather than Nesting, so we don't need add any logic to change between List/Tuple or Map/Object when DynamicPseudoType is involved.
This commit is contained in:
parent
312d798a89
commit
82588af892
|
@ -8,12 +8,35 @@ import (
|
|||
)
|
||||
|
||||
// SetUnknowns takes a cty.Value, and compares it to the schema setting any null
|
||||
// leaf values which are computed as unknown.
|
||||
// values which are computed to unknown.
|
||||
func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
||||
if val.IsNull() || !val.IsKnown() {
|
||||
if !val.IsKnown() {
|
||||
return val
|
||||
}
|
||||
|
||||
// If the object was null, we still need to handle the top level attributes
|
||||
// which might be computed, but we don't need to expand the blocks.
|
||||
if val.IsNull() {
|
||||
objMap := map[string]cty.Value{}
|
||||
allNull := true
|
||||
for name, attr := range schema.Attributes {
|
||||
switch {
|
||||
case attr.Computed:
|
||||
objMap[name] = cty.UnknownVal(attr.Type)
|
||||
allNull = false
|
||||
default:
|
||||
objMap[name] = cty.NullVal(attr.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// If this object has no unknown attributes, then we can leave it null.
|
||||
if allNull {
|
||||
return val
|
||||
}
|
||||
|
||||
return cty.ObjectVal(objMap)
|
||||
}
|
||||
|
||||
valMap := val.AsValueMap()
|
||||
newVals := make(map[string]cty.Value)
|
||||
|
||||
|
@ -35,12 +58,18 @@ func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
|||
continue
|
||||
}
|
||||
|
||||
blockType := blockS.Block.ImpliedType()
|
||||
blockValType := blockVal.Type()
|
||||
blockElementType := blockS.Block.ImpliedType()
|
||||
|
||||
switch blockS.Nesting {
|
||||
case configschema.NestingSingle:
|
||||
// This switches on the value type here, so we can correctly switch
|
||||
// between Tuples/Lists and Maps/Objects.
|
||||
switch {
|
||||
case blockS.Nesting == configschema.NestingSingle:
|
||||
// NestingSingle is the only exception here, where we treat the
|
||||
// block directly as an object
|
||||
newVals[name] = SetUnknowns(blockVal, &blockS.Block)
|
||||
case configschema.NestingSet, configschema.NestingList:
|
||||
|
||||
case blockValType.IsSetType(), blockValType.IsListType(), blockValType.IsTupleType():
|
||||
listVals := blockVal.AsValueSlice()
|
||||
newListVals := make([]cty.Value, 0, len(listVals))
|
||||
|
||||
|
@ -48,24 +77,26 @@ func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
|||
newListVals = append(newListVals, SetUnknowns(v, &blockS.Block))
|
||||
}
|
||||
|
||||
switch blockS.Nesting {
|
||||
case configschema.NestingSet:
|
||||
switch {
|
||||
case blockValType.IsSetType():
|
||||
switch len(newListVals) {
|
||||
case 0:
|
||||
newVals[name] = cty.SetValEmpty(blockType)
|
||||
newVals[name] = cty.SetValEmpty(blockElementType)
|
||||
default:
|
||||
newVals[name] = cty.SetVal(newListVals)
|
||||
}
|
||||
case configschema.NestingList:
|
||||
case blockValType.IsListType():
|
||||
switch len(newListVals) {
|
||||
case 0:
|
||||
newVals[name] = cty.ListValEmpty(blockType)
|
||||
newVals[name] = cty.ListValEmpty(blockElementType)
|
||||
default:
|
||||
newVals[name] = cty.ListVal(newListVals)
|
||||
}
|
||||
case blockValType.IsTupleType():
|
||||
newVals[name] = cty.TupleVal(newListVals)
|
||||
}
|
||||
|
||||
case configschema.NestingMap:
|
||||
case blockValType.IsMapType(), blockValType.IsObjectType():
|
||||
mapVals := blockVal.AsValueMap()
|
||||
newMapVals := make(map[string]cty.Value)
|
||||
|
||||
|
@ -73,15 +104,26 @@ func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
|||
newMapVals[k] = SetUnknowns(v, &blockS.Block)
|
||||
}
|
||||
|
||||
switch {
|
||||
case blockValType.IsMapType():
|
||||
switch len(newMapVals) {
|
||||
case 0:
|
||||
newVals[name] = cty.MapValEmpty(blockType)
|
||||
newVals[name] = cty.MapValEmpty(blockElementType)
|
||||
default:
|
||||
newVals[name] = cty.MapVal(newMapVals)
|
||||
}
|
||||
case blockValType.IsObjectType():
|
||||
if len(newMapVals) == 0 {
|
||||
// We need to populate empty values to make a valid object.
|
||||
for attr, ty := range blockElementType.AttributeTypes() {
|
||||
newMapVals[attr] = cty.NullVal(ty)
|
||||
}
|
||||
}
|
||||
newVals[name] = cty.ObjectVal(newMapVals)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("failed to set unknown values for nested block %q", name))
|
||||
panic(fmt.Sprintf("failed to set unknown values for nested block %q:%#v", name, blockValType))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,14 +50,27 @@ func TestSetUnknowns(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{}),
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"foo": cty.String,
|
||||
"bar": cty.String,
|
||||
"baz": cty.Object(map[string]cty.Type{
|
||||
"boz": cty.String,
|
||||
"biz": cty.String,
|
||||
}),
|
||||
})),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.NullVal(cty.String),
|
||||
"bar": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
},
|
||||
"no prior with set": {
|
||||
// the set value should remain null
|
||||
"null stays null": {
|
||||
// if the object has no computed attributes, it should stay null
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": &configschema.Attribute{
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"baz": {
|
||||
Nesting: configschema.NestingSet,
|
||||
|
@ -73,8 +86,52 @@ func TestSetUnknowns(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{}),
|
||||
cty.ObjectVal(map[string]cty.Value{}),
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"foo": cty.String,
|
||||
"baz": cty.Set(cty.Object(map[string]cty.Type{
|
||||
"boz": cty.String,
|
||||
})),
|
||||
})),
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"foo": cty.String,
|
||||
"baz": cty.Set(cty.Object(map[string]cty.Type{
|
||||
"boz": cty.String,
|
||||
})),
|
||||
})),
|
||||
},
|
||||
"no prior with set": {
|
||||
// the set value should remain null
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": &configschema.Attribute{
|
||||
Type: cty.String,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"baz": {
|
||||
Nesting: configschema.NestingSet,
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"boz": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"foo": cty.String,
|
||||
"baz": cty.Set(cty.Object(map[string]cty.Type{
|
||||
"boz": cty.String,
|
||||
})),
|
||||
})),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
},
|
||||
"prior attributes": {
|
||||
&configschema.Block{
|
||||
|
@ -329,24 +386,97 @@ func TestSetUnknowns(t *testing.T) {
|
|||
}),
|
||||
}),
|
||||
},
|
||||
"prior nested list with dynamic": {
|
||||
&configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"foo": {
|
||||
Nesting: configschema.NestingList,
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"baz": {
|
||||
Type: cty.DynamicPseudoType,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.NullVal(cty.String),
|
||||
"baz": cty.NumberIntVal(8),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.UnknownVal(cty.String),
|
||||
"baz": cty.NumberIntVal(8),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
"prior nested map with dynamic": {
|
||||
&configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"foo": {
|
||||
Nesting: configschema.NestingMap,
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"baz": {
|
||||
Type: cty.DynamicPseudoType,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("beep"),
|
||||
"baz": cty.NullVal(cty.DynamicPseudoType),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("boop"),
|
||||
"baz": cty.NumberIntVal(8),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("beep"),
|
||||
"baz": cty.UnknownVal(cty.DynamicPseudoType),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.StringVal("boop"),
|
||||
"baz": cty.NumberIntVal(8),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
} {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
// coerce the values because SetUnknowns expects the values to be
|
||||
// complete, and so we can take shortcuts writing them in the
|
||||
// test.
|
||||
v, err := tc.Schema.CoerceValue(tc.Val)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected, err := tc.Schema.CoerceValue(tc.Expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := SetUnknowns(v, tc.Schema)
|
||||
if !got.RawEquals(expected) {
|
||||
t.Fatalf("\nexpected: %#v\ngot: %#v\n", expected, got)
|
||||
got := SetUnknowns(tc.Val, tc.Schema)
|
||||
if !got.RawEquals(tc.Expected) {
|
||||
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.Expected, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue