gohcl: allow optional attributes to be specified via struct tag

Previously we required optional attributes to be specified as pointers so that we could represent the empty vs. absent distinction.

For applications that don't need to make that distinction, representing "optional" as a struct tag is more convenient.
This commit is contained in:
Nicholas Jackson 2018-02-17 18:36:04 +00:00 committed by Martin Atkins
parent eea3a14a71
commit 23fc060132
3 changed files with 33 additions and 2 deletions

View File

@ -54,7 +54,17 @@ func TestDecodeBody(t *testing.T) {
Name *string `hcl:"name"` Name *string `hcl:"name"`
}{}), }{}),
0, 0,
}, }, // name nil
{
map[string]interface{}{},
struct {
Name string `hcl:"name,optional"`
}{},
deepEquals(struct {
Name string `hcl:"name,optional"`
}{}),
0,
}, // name optional
{ {
map[string]interface{}{}, map[string]interface{}{},
withNameExpression{}, withNameExpression{},

View File

@ -42,7 +42,9 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
sort.Strings(attrNames) sort.Strings(attrNames)
for _, n := range attrNames { for _, n := range attrNames {
idx := tags.Attributes[n] idx := tags.Attributes[n]
optional := tags.Optional[n]
field := ty.Field(idx) field := ty.Field(idx)
var required bool var required bool
switch { switch {
@ -51,7 +53,7 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
// indicated via a null value, so we don't specify that // indicated via a null value, so we don't specify that
// the field is required during decoding. // the field is required during decoding.
required = false required = false
case field.Type.Kind() != reflect.Ptr: case field.Type.Kind() != reflect.Ptr && !optional:
required = true required = true
default: default:
required = false required = false
@ -111,6 +113,7 @@ type fieldTags struct {
Blocks map[string]int Blocks map[string]int
Labels []labelField Labels []labelField
Remain *int Remain *int
Optional map[string]bool
} }
type labelField struct { type labelField struct {
@ -122,6 +125,7 @@ func getFieldTags(ty reflect.Type) *fieldTags {
ret := &fieldTags{ ret := &fieldTags{
Attributes: map[string]int{}, Attributes: map[string]int{},
Blocks: map[string]int{}, Blocks: map[string]int{},
Optional: map[string]bool{},
} }
ct := ty.NumField() ct := ty.NumField()
@ -158,6 +162,9 @@ func getFieldTags(ty reflect.Type) *fieldTags {
} }
idx := i // copy, because this loop will continue assigning to i idx := i // copy, because this loop will continue assigning to i
ret.Remain = &idx ret.Remain = &idx
case "optional":
ret.Attributes[name] = i
ret.Optional[name] = true
default: default:
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name)) panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
} }

View File

@ -193,6 +193,20 @@ func TestImpliedBodySchema(t *testing.T) {
}, },
false, false,
}, },
{
struct {
Meh string `hcl:"meh,optional"`
}{},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "meh",
Required: false,
},
},
},
false,
},
} }
for _, test := range tests { for _, test := range tests {