config/configschema: Block.ImpliedType method

This returns a cty.Type that the caller can expect to recieve when
decoding a value using the (not yet implemented) decoder specification
for a given schema.
This commit is contained in:
Martin Atkins 2017-10-02 18:48:40 -07:00
parent f117906bdb
commit d0d829848a
2 changed files with 161 additions and 1 deletions

View File

@ -12,5 +12,41 @@ import (
// tested using the InternalValidate method to detect any inconsistencies
// that would cause this method to fall back on defaults and assumptions.
func (b *Block) ImpliedType() cty.Type {
return cty.DynamicPseudoType
if b == nil {
return cty.EmptyObject
}
attrTypes := map[string]cty.Type{}
for name, attrS := range b.Attributes {
attrTypes[name] = attrS.Type
}
for name, blockS := range b.BlockTypes {
if _, exists := attrTypes[name]; exists {
// This indicates an invalid schema, since it's not valid to
// define both an attribute and a block type of the same name.
// However, we don't raise this here since it's checked by
// InternalValidate.
continue
}
childType := blockS.Block.ImpliedType()
switch blockS.Nesting {
case NestingSingle:
attrTypes[name] = childType
case NestingList:
attrTypes[name] = cty.List(childType)
case NestingSet:
attrTypes[name] = cty.Set(childType)
case NestingMap:
attrTypes[name] = cty.Map(childType)
default:
// Invalid nesting type is just ignored. It's checked by
// InternalValidate.
continue
}
}
return cty.Object(attrTypes)
}

View File

@ -0,0 +1,124 @@
package configschema
import (
"testing"
"github.com/zclconf/go-cty/cty"
)
func TestBlockImpliedType(t *testing.T) {
tests := map[string]struct {
Schema *Block
Want cty.Type
}{
"nil": {
nil,
cty.EmptyObject,
},
"empty": {
&Block{},
cty.EmptyObject,
},
"attributes": {
&Block{
Attributes: map[string]*Attribute{
"optional": {
Type: cty.String,
Optional: true,
},
"required": {
Type: cty.Number,
Required: true,
},
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
},
},
},
cty.Object(map[string]cty.Type{
"optional": cty.String,
"required": cty.Number,
"computed": cty.List(cty.Bool),
"optional_computed": cty.Map(cty.Bool),
}),
},
"blocks": {
&Block{
BlockTypes: map[string]*NestedBlock{
"single": &NestedBlock{
Nesting: NestingSingle,
Block: Block{
Attributes: map[string]*Attribute{
"foo": {
Type: cty.DynamicPseudoType,
Required: true,
},
},
},
},
"list": &NestedBlock{
Nesting: NestingList,
},
"set": &NestedBlock{
Nesting: NestingSet,
},
"map": &NestedBlock{
Nesting: NestingMap,
},
},
},
cty.Object(map[string]cty.Type{
"single": cty.Object(map[string]cty.Type{
"foo": cty.DynamicPseudoType,
}),
"list": cty.List(cty.EmptyObject),
"set": cty.Set(cty.EmptyObject),
"map": cty.Map(cty.EmptyObject),
}),
},
"deep block nesting": {
&Block{
BlockTypes: map[string]*NestedBlock{
"single": &NestedBlock{
Nesting: NestingSingle,
Block: Block{
BlockTypes: map[string]*NestedBlock{
"list": &NestedBlock{
Nesting: NestingList,
Block: Block{
BlockTypes: map[string]*NestedBlock{
"set": &NestedBlock{
Nesting: NestingSet,
},
},
},
},
},
},
},
},
},
cty.Object(map[string]cty.Type{
"single": cty.Object(map[string]cty.Type{
"list": cty.List(cty.Object(map[string]cty.Type{
"set": cty.Set(cty.EmptyObject),
})),
}),
}),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got := test.Schema.ImpliedType()
if !got.Equals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}