Skip to content

Commit a42bb0d

Browse files
committed
refactoring ConfigValue concept to allow adding / overwriting mappers
1 parent 0e74dc4 commit a42bb0d

File tree

4 files changed

+247
-80
lines changed

4 files changed

+247
-80
lines changed

acctest/config.go

Lines changed: 102 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,114 @@ package acctest
33
import (
44
"fmt"
55
"strings"
6+
"sync"
7+
8+
"github.com/dcarbone/terraform-plugin-framework-utils/internal/util"
69
)
710

811
type ConfigLiteral string
912

13+
type ConfigValueFunc func(interface{}) string
14+
15+
var (
16+
configValueFuncsMu sync.RWMutex
17+
configValueFuncs map[string]ConfigValueFunc
18+
)
19+
20+
func SetConfigValueFunc(t interface{}, fn ConfigValueFunc) {
21+
configValueFuncsMu.Lock()
22+
defer configValueFuncsMu.Unlock()
23+
configValueFuncs[util.KeyFN(t)] = fn
24+
}
25+
26+
func GetConfigValueFunc(t interface{}) (ConfigValueFunc, bool) {
27+
configValueFuncsMu.RLock()
28+
defer configValueFuncsMu.RUnlock()
29+
fn, ok := configValueFuncs[util.KeyFN(t)]
30+
return fn, ok
31+
}
32+
33+
func DefaultConfigValueFuncs() map[string]ConfigValueFunc {
34+
return map[string]ConfigValueFunc{
35+
// nil
36+
util.KeyFN(nil): func(_ interface{}) string { return "null" },
37+
38+
// values to print literally
39+
util.KeyFN(ConfigLiteral("")): func(v interface{}) string { return string(v.(ConfigLiteral)) },
40+
41+
// simple conversions
42+
util.KeyFN(false): func(v interface{}) string { return fmt.Sprintf("%t", v.(bool)) },
43+
util.KeyFN(0): func(v interface{}) string { return fmt.Sprintf("%d", v.(int)) },
44+
util.KeyFN(float64(0)): func(v interface{}) string { return fmt.Sprintf("%f", v.(float64)) },
45+
46+
// handle single and multi-line strings
47+
util.KeyFN(""): func(v interface{}) string {
48+
if strings.Contains(v.(string), "\n") {
49+
return fmt.Sprintf("<<EOD\n%s\nEOD", v.(string))
50+
} else {
51+
return fmt.Sprintf("%q", v.(string))
52+
}
53+
},
54+
55+
// slices
56+
57+
util.KeyFN(make([]interface{}, 0)): func(v interface{}) string {
58+
formatted := make([]string, 0)
59+
for _, v := range v.([]interface{}) {
60+
formatted = append(formatted, ConfigValue(v))
61+
}
62+
return fmt.Sprintf("[\n%s\n]", strings.Join(formatted, ",\n"))
63+
},
64+
util.KeyFN(make([]string, 0)): func(v interface{}) string {
65+
formatted := make([]string, 0)
66+
for _, v := range v.([]string) {
67+
formatted = append(formatted, ConfigValue(v))
68+
}
69+
return fmt.Sprintf("[\n%s\n]", strings.Join(formatted, ",\n"))
70+
},
71+
util.KeyFN(make([]int, 0)): func(v interface{}) string {
72+
formatted := make([]string, 0)
73+
for _, v := range v.([]int) {
74+
formatted = append(formatted, ConfigValue(v))
75+
}
76+
return fmt.Sprintf("[\n%s\n]", strings.Join(formatted, ",\n"))
77+
},
78+
util.KeyFN(make([]float64, 0)): func(v interface{}) string {
79+
formatted := make([]string, 0)
80+
for _, v := range v.([]float64) {
81+
formatted = append(formatted, ConfigValue(v))
82+
}
83+
return fmt.Sprintf("[\n%s\n]", strings.Join(formatted, ",\n"))
84+
},
85+
86+
// maps
87+
88+
util.KeyFN(make(map[string]interface{})): func(v interface{}) string {
89+
inner := "{"
90+
for k, v := range v.(map[string]interface{}) {
91+
inner = fmt.Sprintf("%s\n%s = %s", inner, k, ConfigValue(v))
92+
}
93+
return fmt.Sprintf("%s\n}", inner)
94+
},
95+
util.KeyFN(make(map[string]string)): func(v interface{}) string {
96+
inner := "{"
97+
for k, v := range v.(map[string]string) {
98+
inner = fmt.Sprintf("%s\n%s = %s", inner, k, ConfigValue(v))
99+
}
100+
return fmt.Sprintf("%s\n}", inner)
101+
},
102+
}
103+
}
104+
105+
func init() {
106+
configValueFuncs = DefaultConfigValueFuncs()
107+
}
108+
10109
// ConfigValue attempts to convert the provided input to a Terraform config safe representation of its value
11110
func ConfigValue(in interface{}) string {
12-
switch in.(type) {
13-
case nil:
14-
return "null"
15-
16-
case ConfigLiteral:
17-
return string(in.(ConfigLiteral))
18-
19-
case string:
20-
if strings.Contains(in.(string), "\n") {
21-
return fmt.Sprintf("<<EOD\n%s\nEOD", in.(string))
22-
} else {
23-
return fmt.Sprintf("%q", in.(string))
24-
}
25-
26-
case bool:
27-
return fmt.Sprintf("%t", in.(bool))
28-
29-
case int:
30-
return fmt.Sprintf("%d", in.(int))
31-
32-
case float64:
33-
return fmt.Sprintf("%f", in.(float64))
34-
35-
case []interface{}:
36-
formatted := make([]string, 0)
37-
for _, v := range in.([]interface{}) {
38-
formatted = append(formatted, ConfigValue(v))
39-
}
40-
return fmt.Sprintf("[\n%s]", strings.Join(formatted, ",\n"))
41-
case []string:
42-
formatted := make([]string, 0)
43-
for _, v := range in.([]string) {
44-
formatted = append(formatted, ConfigValue(v))
45-
}
46-
return fmt.Sprintf("[\n%s]", strings.Join(formatted, ",\n"))
47-
case []int:
48-
formatted := make([]string, 0)
49-
for _, v := range in.([]int) {
50-
formatted = append(formatted, ConfigValue(v))
51-
}
52-
return fmt.Sprintf("[\n%s]", strings.Join(formatted, ",\n"))
53-
case []float64:
54-
formatted := make([]string, 0)
55-
for _, v := range in.([]float64) {
56-
formatted = append(formatted, ConfigValue(v))
57-
}
58-
return fmt.Sprintf("[\n%s]", strings.Join(formatted, ",\n"))
59-
60-
case map[string]interface{}:
61-
inner := "{"
62-
for k, v := range in.(map[string]interface{}) {
63-
inner = fmt.Sprintf("%s\n%s = %s", inner, k, ConfigValue(v))
64-
}
65-
return fmt.Sprintf("%s\n}", inner)
66-
case map[string]string:
67-
inner := "{"
68-
for k, v := range in.(map[string]string) {
69-
inner = fmt.Sprintf("%s\n%s = %s", inner, k, ConfigValue(v))
70-
}
71-
return fmt.Sprintf("%s\n}", inner)
72-
73-
default:
111+
if fn, ok := GetConfigValueFunc(in); ok {
112+
return fn(in)
113+
} else {
74114
panic(fmt.Sprintf("Unable to handle config values of type %T", in))
75115
}
76116
}

acctest/config_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package acctest_test
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"reflect"
7+
"testing"
8+
9+
"github.com/dcarbone/terraform-plugin-framework-utils/acctest"
10+
)
11+
12+
func TestConfigValue_Defaults(t *testing.T) {
13+
type convTest struct {
14+
in interface{}
15+
out interface{}
16+
}
17+
18+
theTests := []convTest{
19+
{
20+
in: nil,
21+
out: "null",
22+
},
23+
{
24+
in: acctest.ConfigLiteral("local.whateverthing"),
25+
out: "local.whateverthing",
26+
},
27+
{
28+
in: true,
29+
out: "true",
30+
},
31+
{
32+
in: false,
33+
out: "false",
34+
},
35+
{
36+
in: math.MaxInt16,
37+
out: fmt.Sprintf("%d", math.MaxInt16),
38+
},
39+
{
40+
in: -5,
41+
out: "-5",
42+
},
43+
{
44+
in: 0.5,
45+
out: fmt.Sprintf("%f", 0.5),
46+
},
47+
{
48+
in: "single-line",
49+
out: `"single-line"`,
50+
},
51+
{
52+
in: `multi
53+
line`,
54+
out: `<<EOD
55+
multi
56+
line
57+
EOD`,
58+
},
59+
{
60+
in: []interface{}{"hello", 5},
61+
out: `[
62+
"hello",
63+
5
64+
]`,
65+
},
66+
{
67+
in: []string{"hello", "there"},
68+
out: `[
69+
"hello",
70+
"there"
71+
]`,
72+
},
73+
{
74+
in: []int{1, 5},
75+
out: `[
76+
1,
77+
5
78+
]`,
79+
},
80+
{
81+
in: []float64{1, 5},
82+
out: `[
83+
` + fmt.Sprintf("%f", float64(1)) + `,
84+
` + fmt.Sprintf("%f", float64(5)) + `
85+
]`,
86+
},
87+
}
88+
89+
// todo: use biggerer brain to figure out how to test map -> string verification
90+
91+
for _, theT := range theTests {
92+
out := acctest.ConfigValue(theT.in)
93+
if !reflect.DeepEqual(out, theT.out) {
94+
t.Log("Output does not match expected")
95+
t.Logf("Input: %v", theT.in)
96+
t.Logf("Expected: %v", theT.out)
97+
t.Logf("Actual: %v", out)
98+
t.Fail()
99+
}
100+
}
101+
}
102+
103+
func TestConfigValue_Set(t *testing.T) {
104+
int32fn := func(v interface{}) string {
105+
return fmt.Sprintf("%d", v.(int32))
106+
}
107+
acctest.SetConfigValueFunc(int32(0), int32fn)
108+
v := acctest.ConfigValue(int32(1))
109+
if v != "1" {
110+
t.Logf("Expected int32(1) to produce 1, saw %v", v)
111+
t.Fail()
112+
}
113+
}

internal/util/reflect.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
)
7+
8+
func BuildReflectTypeKey(varType reflect.Type) string {
9+
const kfmt = "%s.%s.%s"
10+
if varType == nil {
11+
return "nil"
12+
}
13+
return fmt.Sprintf(
14+
kfmt,
15+
varType.PkgPath(),
16+
varType.Name(),
17+
varType.String(),
18+
)
19+
}
20+
21+
func KeyFN(t interface{}) string {
22+
return BuildReflectTypeKey(reflect.TypeOf(t))
23+
}

validation/comparison.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package validation
33
import (
44
"fmt"
55
"math/big"
6-
"reflect"
76
"sync"
87

98
"github.com/dcarbone/terraform-plugin-framework-utils/conv"
9+
"github.com/dcarbone/terraform-plugin-framework-utils/internal/util"
1010
"github.com/hashicorp/terraform-plugin-framework/attr"
1111
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
1212
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -48,15 +48,6 @@ var (
4848
comparisonFuncs map[string]ComparisonFunc
4949
)
5050

51-
func buildReflectTypeKey(varType reflect.Type) string {
52-
const kfmt = "%s.%s.%s"
53-
return fmt.Sprintf(kfmt, varType.PkgPath(), varType.Name(), varType.String())
54-
}
55-
56-
func kfn(t interface{}) string {
57-
return buildReflectTypeKey(reflect.TypeOf(t))
58-
}
59-
6051
func compareBool(av attr.Value, op CompareOp, target interface{}) error {
6152
switch op {
6253
case Equal:
@@ -216,28 +207,28 @@ func compareString(av attr.Value, op CompareOp, target interface{}) error {
216207
// DefaultComparisonFuncs returns the complete list of default comparison functions
217208
func DefaultComparisonFuncs() map[string]ComparisonFunc {
218209
return map[string]ComparisonFunc{
219-
kfn(false): compareBool,
220-
kfn(0.0): compareFloat64,
221-
kfn(int64(0)): compareInt64,
222-
kfn(0): compareInt,
223-
kfn(new(big.Float)): compareBigFloat,
224-
kfn(""): compareString,
210+
util.KeyFN(false): compareBool,
211+
util.KeyFN(0.0): compareFloat64,
212+
util.KeyFN(int64(0)): compareInt64,
213+
util.KeyFN(0): compareInt,
214+
util.KeyFN(new(big.Float)): compareBigFloat,
215+
util.KeyFN(""): compareString,
225216
}
226217
}
227218

228219
// SetComparisonFunc sets a comparison function to use for comparing attribute values to values of the specified type
229220
func SetComparisonFunc(targetType interface{}, fn ComparisonFunc) {
230221
comparisonFuncsMu.Lock()
231222
defer comparisonFuncsMu.Unlock()
232-
comparisonFuncs[kfn(targetType)] = fn
223+
comparisonFuncs[util.KeyFN(targetType)] = fn
233224
}
234225

235226
// GetComparisonFunc attempts to return a previously registered comparison function for a specified op : type
236227
// combination
237228
func GetComparisonFunc(targetType interface{}) (ComparisonFunc, bool) {
238229
comparisonFuncsMu.Lock()
239230
defer comparisonFuncsMu.Unlock()
240-
if fn, ok := comparisonFuncs[kfn(targetType)]; ok {
231+
if fn, ok := comparisonFuncs[util.KeyFN(targetType)]; ok {
241232
return fn, true
242233
}
243234
return nil, false

0 commit comments

Comments
 (0)