configs/configschema: EmptyValue methods
These helpers determine the value that would be used for a particular schema construct if the configuration construct it represents is not present (or, in the case of *Block, empty) in the configuration. This is different than cty.NullVal on the implied type because it might return non-null "empty" values for certain constructs if their absence would be reported as such during a decode with no required attributes or blocks.
This commit is contained in:
parent
44c9e81d4c
commit
a20084dc0e
|
@ -0,0 +1,57 @@
|
|||
package configschema
|
||||
|
||||
import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// EmptyValue returns the "empty value" for the recieving block, which for
|
||||
// a block type is a non-null object where all of the attribute values are
|
||||
// the empty values of the block's attributes and nested block types.
|
||||
//
|
||||
// In other words, it returns the value that would be returned if an empty
|
||||
// block were decoded against the recieving schema, assuming that no required
|
||||
// attribute or block constraints were honored.
|
||||
func (b *Block) EmptyValue() cty.Value {
|
||||
vals := make(map[string]cty.Value)
|
||||
for name, attrS := range b.Attributes {
|
||||
vals[name] = attrS.EmptyValue()
|
||||
}
|
||||
for name, blockS := range b.BlockTypes {
|
||||
vals[name] = blockS.EmptyValue()
|
||||
}
|
||||
return cty.ObjectVal(vals)
|
||||
}
|
||||
|
||||
// EmptyValue returns the "empty value" for the receiving attribute, which is
|
||||
// the value that would be returned if there were no definition of the attribute
|
||||
// at all, ignoring any required constraint.
|
||||
func (a *Attribute) EmptyValue() cty.Value {
|
||||
return cty.NullVal(a.Type)
|
||||
}
|
||||
|
||||
// EmptyValue returns the "empty value" for when there are zero nested blocks
|
||||
// present of the receiving type.
|
||||
func (b *NestedBlock) EmptyValue() cty.Value {
|
||||
switch b.Nesting {
|
||||
case NestingSingle:
|
||||
return cty.NullVal(b.Block.ImpliedType())
|
||||
case NestingList:
|
||||
if ty := b.Block.ImpliedType(); ty.HasDynamicTypes() {
|
||||
return cty.EmptyTupleVal
|
||||
} else {
|
||||
return cty.ListValEmpty(ty)
|
||||
}
|
||||
case NestingMap:
|
||||
if ty := b.Block.ImpliedType(); ty.HasDynamicTypes() {
|
||||
return cty.EmptyObjectVal
|
||||
} else {
|
||||
return cty.MapValEmpty(ty)
|
||||
}
|
||||
case NestingSet:
|
||||
return cty.SetValEmpty(b.Block.ImpliedType())
|
||||
default:
|
||||
// Should never get here because the above is intended to be exhaustive,
|
||||
// but we'll be robust and return a result nonetheless.
|
||||
return cty.NullVal(cty.DynamicPseudoType)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package configschema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/apparentlymart/go-dump/dump"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestBlockEmptyValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
Schema *Block
|
||||
Want cty.Value
|
||||
}{
|
||||
{
|
||||
&Block{},
|
||||
cty.EmptyObjectVal,
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"str": cty.NullVal(cty.String),
|
||||
}),
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
BlockTypes: map[string]*NestedBlock{
|
||||
"single": {
|
||||
Nesting: NestingSingle,
|
||||
Block: Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"single": cty.NullVal(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
}),
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
BlockTypes: map[string]*NestedBlock{
|
||||
"list": {
|
||||
Nesting: NestingList,
|
||||
Block: Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"list": cty.ListValEmpty(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
}),
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
BlockTypes: map[string]*NestedBlock{
|
||||
"list_dynamic": {
|
||||
Nesting: NestingList,
|
||||
Block: Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.DynamicPseudoType, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"list_dynamic": cty.EmptyTupleVal,
|
||||
}),
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
BlockTypes: map[string]*NestedBlock{
|
||||
"map": {
|
||||
Nesting: NestingMap,
|
||||
Block: Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
}),
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
BlockTypes: map[string]*NestedBlock{
|
||||
"map_dynamic": {
|
||||
Nesting: NestingMap,
|
||||
Block: Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.DynamicPseudoType, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"map_dynamic": cty.EmptyObjectVal,
|
||||
}),
|
||||
},
|
||||
{
|
||||
&Block{
|
||||
BlockTypes: map[string]*NestedBlock{
|
||||
"set": {
|
||||
Nesting: NestingSet,
|
||||
Block: Block{
|
||||
Attributes: map[string]*Attribute{
|
||||
"str": {Type: cty.String, Required: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{
|
||||
"str": cty.String,
|
||||
})),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%#v", test.Schema), func(t *testing.T) {
|
||||
got := test.Schema.EmptyValue()
|
||||
if !test.Want.RawEquals(got) {
|
||||
t.Errorf("wrong result\nschema: %s\ngot: %s\nwant: %s", spew.Sdump(test.Schema), dump.Value(got), dump.Value(test.Want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue