gohcl: Decode missing hcl.Expression as a cty Null
Rather than writing a nil hcl.Expression, as a special case we'll deal with this within the cty type system, which we assume is what the caller wants if they are decoding into a hcl.Expression rather than a native Go type.
This commit is contained in:
parent
339b9cfc34
commit
f44382c4fa
@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
@ -81,11 +83,25 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, attr := range content.Attributes {
|
for name, fieldIdx := range tags.Attributes {
|
||||||
fieldIdx := tags.Attributes[name]
|
attr := content.Attributes[name]
|
||||||
field := val.Type().Field(fieldIdx)
|
field := val.Type().Field(fieldIdx)
|
||||||
fieldV := val.Field(fieldIdx)
|
fieldV := val.Field(fieldIdx)
|
||||||
|
|
||||||
|
if attr == nil {
|
||||||
|
if !exprType.AssignableTo(field.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// As a special case, if the target is of type hcl.Expression then
|
||||||
|
// we'll assign an actual expression that evalues to a cty null,
|
||||||
|
// so the caller can deal with it within the cty realm rather
|
||||||
|
// than within the Go realm.
|
||||||
|
synthExpr := hcl.StaticExpr(cty.NullVal(cty.DynamicPseudoType), body.MissingItemRange())
|
||||||
|
fieldV.Set(reflect.ValueOf(synthExpr))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case attrType.AssignableTo(field.Type):
|
case attrType.AssignableTo(field.Type):
|
||||||
fieldV.Set(reflect.ValueOf(attr))
|
fieldV.Set(reflect.ValueOf(attr))
|
||||||
|
@ -19,6 +19,10 @@ func TestDecodeBody(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type withNameExpression struct {
|
||||||
|
Name hcl.Expression `hcl:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Body map[string]interface{}
|
Body map[string]interface{}
|
||||||
Target interface{}
|
Target interface{}
|
||||||
@ -51,6 +55,60 @@ func TestDecodeBody(t *testing.T) {
|
|||||||
}{}),
|
}{}),
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{},
|
||||||
|
withNameExpression{},
|
||||||
|
func(v interface{}) bool {
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
wne, valid := v.(withNameExpression)
|
||||||
|
if !valid {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if wne.Name == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
nameVal, _ := wne.Name.Value(nil)
|
||||||
|
if !nameVal.IsNull() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "Ermintrude",
|
||||||
|
},
|
||||||
|
withNameExpression{},
|
||||||
|
func(v interface{}) bool {
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
wne, valid := v.(withNameExpression)
|
||||||
|
if !valid {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if wne.Name == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
nameVal, _ := wne.Name.Value(nil)
|
||||||
|
if !nameVal.Equals(cty.StringVal("Ermintrude")).True() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "Ermintrude",
|
"name": "Ermintrude",
|
||||||
|
@ -43,9 +43,23 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
|
|||||||
for _, n := range attrNames {
|
for _, n := range attrNames {
|
||||||
idx := tags.Attributes[n]
|
idx := tags.Attributes[n]
|
||||||
field := ty.Field(idx)
|
field := ty.Field(idx)
|
||||||
|
var required bool
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case field.Type.AssignableTo(exprType):
|
||||||
|
// If we're decoding to hcl.Expression then absense can be
|
||||||
|
// indicated via a null value, so we don't specify that
|
||||||
|
// the field is required during decoding.
|
||||||
|
required = false
|
||||||
|
case field.Type.Kind() != reflect.Ptr:
|
||||||
|
required = true
|
||||||
|
default:
|
||||||
|
required = false
|
||||||
|
}
|
||||||
|
|
||||||
attrSchemas = append(attrSchemas, hcl.AttributeSchema{
|
attrSchemas = append(attrSchemas, hcl.AttributeSchema{
|
||||||
Name: n,
|
Name: n,
|
||||||
Required: field.Type.Kind() != reflect.Ptr,
|
Required: required,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +179,20 @@ func TestImpliedBodySchema(t *testing.T) {
|
|||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
Expr hcl.Expression `hcl:"expr"`
|
||||||
|
}{},
|
||||||
|
&hcl.BodySchema{
|
||||||
|
Attributes: []hcl.AttributeSchema{
|
||||||
|
{
|
||||||
|
Name: "expr",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
Loading…
Reference in New Issue
Block a user