mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 00:04:50 +08:00
fix(hcl): coerce non-string object keys to strings instead of panicking (#2795)
HCL object keys may be non-string literals (e.g. a number like 1). The decoder called cty.Value.AsString on the evaluated key, which panics with 'not a string' for any non-string key type. Mirror OpenTofu/Terragrunt by silently converting non-string keys to their string representation via cty/convert. Add decode scenarios covering numeric and boolean keys.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
)
|
||||
|
||||
type hclDecoder struct {
|
||||
@@ -252,6 +253,22 @@ func addBlockToMapping(parent *CandidateNode, block *hclsyntax.Block, src []byte
|
||||
}
|
||||
}
|
||||
|
||||
// hclKeyValueAsString converts an evaluated HCL key expression to its string
|
||||
// representation. HCL object keys may be non-string literals (e.g. a number
|
||||
// like `1`); calling AsString on those panics. Mirroring OpenTofu/Terragrunt,
|
||||
// we silently coerce non-string keys to their string form ("1" -> "1",
|
||||
// true -> "true") instead of panicking.
|
||||
func hclKeyValueAsString(keyVal cty.Value) (string, error) {
|
||||
if keyVal.Type() == cty.String {
|
||||
return keyVal.AsString(), nil
|
||||
}
|
||||
strVal, err := convert.Convert(keyVal, cty.String)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strVal.AsString(), nil
|
||||
}
|
||||
|
||||
func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode {
|
||||
// handle literal values directly
|
||||
switch e := expr.(type) {
|
||||
@@ -338,7 +355,19 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
||||
}
|
||||
continue
|
||||
}
|
||||
keyStr := keyVal.AsString()
|
||||
keyStr, err := hclKeyValueAsString(keyVal)
|
||||
if err != nil {
|
||||
// fallback: try to extract key from source
|
||||
r := item.KeyExpr.Range()
|
||||
start := r.Start.Byte
|
||||
end := r.End.Byte
|
||||
if start >= 0 && end >= start && end <= len(src) {
|
||||
keyNode := createStringScalarNode(strings.TrimSpace(string(src[start:end])))
|
||||
valNode := convertHclExprToNode(item.ValueExpr, src)
|
||||
m.AddKeyValueChild(keyNode, valNode)
|
||||
}
|
||||
continue
|
||||
}
|
||||
keyNode := createStringScalarNode(keyStr)
|
||||
valNode := convertHclExprToNode(item.ValueExpr, src)
|
||||
m.AddKeyValueChild(keyNode, valNode)
|
||||
|
||||
@@ -472,6 +472,20 @@ var hclFormatScenarios = []formatScenario{
|
||||
expected: "service {\n optional_field = null\n}\n",
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
description: "Non-string object keys are coerced to strings",
|
||||
skipDoc: true,
|
||||
input: `intdict = { 1 = {} }`,
|
||||
expected: "intdict: {\"1\": {}}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "Mixed non-string object keys are coerced to strings",
|
||||
skipDoc: true,
|
||||
input: `d = { 1 = "a", 2 = "b", true = "c" }`,
|
||||
expected: "d: {\"1\": \"a\", \"2\": \"b\", \"true\": \"c\"}\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
}
|
||||
|
||||
func testHclScenario(t *testing.T, s formatScenario) {
|
||||
|
||||
Reference in New Issue
Block a user