Skip to content

Commit

Permalink
gohcl: allow optional attributes to be specified via struct tag
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nicholasjackson authored and apparentlymart committed Feb 17, 2018
1 parent eea3a14 commit 23fc060
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 2 deletions.
12 changes: 11 additions & 1 deletion gohcl/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@ func TestDecodeBody(t *testing.T) {
Name *string `hcl:"name"`
}{}),
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{}{},
withNameExpression{},
Expand Down
9 changes: 8 additions & 1 deletion gohcl/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
sort.Strings(attrNames)
for _, n := range attrNames {
idx := tags.Attributes[n]
optional := tags.Optional[n]
field := ty.Field(idx)

var required bool

switch {
Expand All @@ -51,7 +53,7 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
// 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:
case field.Type.Kind() != reflect.Ptr && !optional:
required = true
default:
required = false
Expand Down Expand Up @@ -111,6 +113,7 @@ type fieldTags struct {
Blocks map[string]int
Labels []labelField
Remain *int
Optional map[string]bool
}

type labelField struct {
Expand All @@ -122,6 +125,7 @@ func getFieldTags(ty reflect.Type) *fieldTags {
ret := &fieldTags{
Attributes: map[string]int{},
Blocks: map[string]int{},
Optional: map[string]bool{},
}

ct := ty.NumField()
Expand Down Expand Up @@ -158,6 +162,9 @@ func getFieldTags(ty reflect.Type) *fieldTags {
}
idx := i // copy, because this loop will continue assigning to i
ret.Remain = &idx
case "optional":
ret.Attributes[name] = i
ret.Optional[name] = true
default:
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
}
Expand Down
14 changes: 14 additions & 0 deletions gohcl/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@ func TestImpliedBodySchema(t *testing.T) {
},
false,
},
{
struct {
Meh string `hcl:"meh,optional"`
}{},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "meh",
Required: false,
},
},
},
false,
},
}

for _, test := range tests {
Expand Down

0 comments on commit 23fc060

Please sign in to comment.