diff --git a/plan.go b/plan.go index de2b67c..bcf44fa 100644 --- a/plan.go +++ b/plan.go @@ -182,7 +182,10 @@ func toCtyValue(a any) (cty.Value, error) { } sv = append(sv, v) } - return cty.ListVal(sv), nil + + // Always use a tuple over a list. Tuples are heterogeneous typed lists, which is + // more robust. Functionally equivalent for our use case of looking up values. + return cty.TupleVal(sv), nil case reflect.Map: if av.Type().Key().Kind() != reflect.String { return cty.NilVal, fmt.Errorf("map keys must be string, found %q", av.Type().Key().Kind()) diff --git a/plan_internal_test.go b/plan_internal_test.go new file mode 100644 index 0000000..fe86220 --- /dev/null +++ b/plan_internal_test.go @@ -0,0 +1,32 @@ +package preview + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" +) + +func Test_toCtyValue(t *testing.T) { + t.Parallel() + + t.Run("EmptyList", func(t *testing.T) { + t.Parallel() + val, err := toCtyValue([]any{}) + require.NoError(t, err) + require.True(t, val.Type().IsTupleType()) + }) + + t.Run("HeterogeneousList", func(t *testing.T) { + t.Parallel() + val, err := toCtyValue([]any{5, "hello", true}) + require.NoError(t, err) + require.True(t, val.Type().IsTupleType()) + require.Equal(t, 3, val.LengthInt()) + require.True(t, val.Equals(cty.TupleVal([]cty.Value{ + cty.NumberIntVal(5), + cty.StringVal("hello"), + cty.BoolVal(true), + })).True()) + }) +}