Open icholy opened 4 years ago
Alternatively, gohcl could allow registering mapping/conversions for specific types.
func RegisterDecoder(t reflect.Type, convert func(hcl.Expression) (interface{}, error)) {}
func RegisterEncoder(t reflect.Type, convert func(interface{}) (hcl.Expression, error)) {}
Adding links for the benefit of others spelunking this issue:
https://github.com/hashicorp/hcl/issues/89 (and many linked there)
https://github.com/hashicorp/hcl/pull/203
One nasty side effect of this is that forcing users to parse/validate e.g. a timestamp in a string after the parse means any error reported from that will no longer be able to point the exact source file location.
Is there any canonical alternative way to do this? I've tried reading through the documentation as well as poking through the source for Packer and Vault, but those all seem far more complex implementations compared to what I'm trying to do. Generally, I want to use the standard decoder but provide some way to decode a custom type
I got this from nomad's code
// from https://github.com/hashicorp/nomad/blob/0ccf942b26f8c47582f18f324114d02d0bb03a43/jobspec2/hcl_conversions.go
package main
import (
"fmt"
"reflect"
"time"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
var hclDecoder *gohcl.Decoder
func init() {
hclDecoder = newHCLDecoder()
}
func newHCLDecoder() *gohcl.Decoder {
decoder := &gohcl.Decoder{}
// time conversion
d := time.Duration(0)
decoder.RegisterExpressionDecoder(reflect.TypeOf(d), decodeDuration)
return decoder
}
func decodeDuration(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
srcVal, diags := expr.Value(ctx)
if srcVal.Type() == cty.String {
dur, err := time.ParseDuration(srcVal.AsString())
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsuitable value type",
Detail: fmt.Sprintf("Unsuitable duration value: %s", err.Error()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
return diags
}
srcVal = cty.NumberIntVal(int64(dur))
}
if srcVal.Type() != cty.Number {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsuitable value type",
Detail: fmt.Sprintf("Unsuitable value: expected a string but found %s", srcVal.Type()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
return diags
}
err := gocty.FromCtyValue(srcVal, val)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsuitable value type",
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
}
return diags
}
The functions used above like RegisterExpressionDecoder
are available in v2.9.2-0.20220525143345-ab3cae0737bc
. I didn't check the other tags but it is not available on the versions 2.9.1
, 2.10.0
and 2.18.1
then on how to use it
# version from https://github.com/hashicorp/nomad/blob/0ccf942b26f8c47582f18f324114d02d0bb03a43/go.mod#L76
go get github.com/hashicorp/hcl/v2/hclparse@v2.9.2-0.20220525143345-ab3cae0737bc
go mod tidy
# go mod vendor
package main
import (
"log"
"time"
"github.com/hashicorp/hcl/v2/hclparse"
)
type Config struct {
Duration time.Duration `hcl:"duration"`
}
var input []byte = []byte(`duration = "5s"`)
func main() {
parser := hclparse.NewParser()
f, parseDiag := parser.ParseHCL(input, "dummy.hcl")
if parseDiag.HasErrors() {
log.Fatal(parseDiag.Error())
}
conf := Config{}
decodeDiag := hclDecoder.DecodeBody(f.Body, nil, &conf)
if decodeDiag.HasErrors() {
log.Fatal(decodeDiag.Error())
}
log.Printf("conf: %#v", conf)
}
$ go run ./
2023/10/06 15:28:23 conf: main.Config{Duration:5000000000}
Note, if you searching for this code, it's only available in a diff branch containing specific tweaks for Nomad. Use:
go get github.com/hashicorp/hcl/v2@nomad-v2.20.1+tweaks
Is there a way to decode custom types? I'm looking for something equivalent to
encoding/json.Unmarshaler
. I've tried implementingencoding.TextUnmarshaler
which didn't work.Here's an example use-case.
SO Question: https://stackoverflow.com/questions/60515131/hcl-unmarshal-custom-types