Skip to content

Commit 1954b67

Browse files
committed
refactor dynamic type conversion
1 parent 3169aec commit 1954b67

File tree

3 files changed

+209
-89
lines changed

3 files changed

+209
-89
lines changed

types/basetypes/dynamic_type.go

Lines changed: 1 addition & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func (t DynamicType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (
9191
return nil, errors.New("ambiguous known value for `tftypes.DynamicPseudoType` detected")
9292
}
9393

94-
attrType, err := tftypeToFrameworkType(in.Type())
94+
attrType, err := TerraformTypeToFrameworkType(in.Type())
9595
if err != nil {
9696
return nil, err
9797
}
@@ -108,91 +108,3 @@ func (t DynamicType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (
108108
func (t DynamicType) ValueType(_ context.Context) attr.Value {
109109
return DynamicValue{}
110110
}
111-
112-
// tftypeToFrameworkType is a helper function that returns the framework type equivalent for a given Terraform type.
113-
//
114-
// Custom dynamic type implementations shouldn't need to override this method, but if needed, they can implement similar logic
115-
// in their `ValueFromTerraform` implementation.
116-
func tftypeToFrameworkType(in tftypes.Type) (attr.Type, error) {
117-
// Primitive types
118-
if in.Is(tftypes.Bool) {
119-
return BoolType{}, nil
120-
}
121-
if in.Is(tftypes.Number) {
122-
return NumberType{}, nil
123-
}
124-
if in.Is(tftypes.String) {
125-
return StringType{}, nil
126-
}
127-
128-
if in.Is(tftypes.DynamicPseudoType) {
129-
// Null and Unknown values that do not have a type determined will have a type of DynamicPseudoType
130-
return DynamicType{}, nil
131-
}
132-
133-
// Collection types
134-
if in.Is(tftypes.List{}) {
135-
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
136-
l := in.(tftypes.List)
137-
138-
elemType, err := tftypeToFrameworkType(l.ElementType)
139-
if err != nil {
140-
return nil, err
141-
}
142-
return ListType{ElemType: elemType}, nil
143-
}
144-
if in.Is(tftypes.Map{}) {
145-
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
146-
m := in.(tftypes.Map)
147-
148-
elemType, err := tftypeToFrameworkType(m.ElementType)
149-
if err != nil {
150-
return nil, err
151-
}
152-
153-
return MapType{ElemType: elemType}, nil
154-
}
155-
if in.Is(tftypes.Set{}) {
156-
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
157-
s := in.(tftypes.Set)
158-
159-
elemType, err := tftypeToFrameworkType(s.ElementType)
160-
if err != nil {
161-
return nil, err
162-
}
163-
164-
return SetType{ElemType: elemType}, nil
165-
}
166-
167-
// Structural types
168-
if in.Is(tftypes.Object{}) {
169-
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
170-
o := in.(tftypes.Object)
171-
172-
attrTypes := make(map[string]attr.Type, len(o.AttributeTypes))
173-
for name, tfType := range o.AttributeTypes {
174-
t, err := tftypeToFrameworkType(tfType)
175-
if err != nil {
176-
return nil, err
177-
}
178-
attrTypes[name] = t
179-
}
180-
return ObjectType{AttrTypes: attrTypes}, nil
181-
}
182-
if in.Is(tftypes.Tuple{}) {
183-
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
184-
tup := in.(tftypes.Tuple)
185-
186-
elemTypes := make([]attr.Type, len(tup.ElementTypes))
187-
for i, tfType := range tup.ElementTypes {
188-
t, err := tftypeToFrameworkType(tfType)
189-
if err != nil {
190-
return nil, err
191-
}
192-
elemTypes[i] = t
193-
}
194-
return TupleType{ElemTypes: elemTypes}, nil
195-
}
196-
197-
return nil, fmt.Errorf("unsupported tftypes.Type detected: %T", in)
198-
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package basetypes
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
)
12+
13+
// TerraformTypeToFrameworkType is a helper function that returns the framework type equivalent for a given Terraform type.
14+
func TerraformTypeToFrameworkType(in tftypes.Type) (attr.Type, error) {
15+
// Primitive types
16+
if in.Is(tftypes.Bool) {
17+
return BoolType{}, nil
18+
}
19+
if in.Is(tftypes.Number) {
20+
return NumberType{}, nil
21+
}
22+
if in.Is(tftypes.String) {
23+
return StringType{}, nil
24+
}
25+
26+
if in.Is(tftypes.DynamicPseudoType) {
27+
return DynamicType{}, nil
28+
}
29+
30+
// Collection types
31+
if in.Is(tftypes.List{}) {
32+
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
33+
l := in.(tftypes.List)
34+
35+
elemType, err := TerraformTypeToFrameworkType(l.ElementType)
36+
if err != nil {
37+
return nil, err
38+
}
39+
return ListType{ElemType: elemType}, nil
40+
}
41+
if in.Is(tftypes.Map{}) {
42+
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
43+
m := in.(tftypes.Map)
44+
45+
elemType, err := TerraformTypeToFrameworkType(m.ElementType)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
return MapType{ElemType: elemType}, nil
51+
}
52+
if in.Is(tftypes.Set{}) {
53+
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
54+
s := in.(tftypes.Set)
55+
56+
elemType, err := TerraformTypeToFrameworkType(s.ElementType)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
return SetType{ElemType: elemType}, nil
62+
}
63+
64+
// Structural types
65+
if in.Is(tftypes.Object{}) {
66+
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
67+
o := in.(tftypes.Object)
68+
69+
attrTypes := make(map[string]attr.Type, len(o.AttributeTypes))
70+
for name, tfType := range o.AttributeTypes {
71+
t, err := TerraformTypeToFrameworkType(tfType)
72+
if err != nil {
73+
return nil, err
74+
}
75+
attrTypes[name] = t
76+
}
77+
return ObjectType{AttrTypes: attrTypes}, nil
78+
}
79+
if in.Is(tftypes.Tuple{}) {
80+
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
81+
tup := in.(tftypes.Tuple)
82+
83+
elemTypes := make([]attr.Type, len(tup.ElementTypes))
84+
for i, tfType := range tup.ElementTypes {
85+
t, err := TerraformTypeToFrameworkType(tfType)
86+
if err != nil {
87+
return nil, err
88+
}
89+
elemTypes[i] = t
90+
}
91+
return TupleType{ElemTypes: elemTypes}, nil
92+
}
93+
94+
return nil, fmt.Errorf("unsupported tftypes.Type detected: %T", in)
95+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package basetypes
5+
6+
import (
7+
"testing"
8+
9+
"github.com/google/go-cmp/cmp"
10+
"github.com/hashicorp/terraform-plugin-framework/attr"
11+
"github.com/hashicorp/terraform-plugin-go/tftypes"
12+
)
13+
14+
func TestTerraformTypeToFrameworkType(t *testing.T) {
15+
t.Parallel()
16+
17+
tests := map[string]struct {
18+
input tftypes.Type
19+
expected attr.Type
20+
}{
21+
"bool": {
22+
input: tftypes.Bool,
23+
expected: BoolType{},
24+
},
25+
"number": {
26+
input: tftypes.Number,
27+
expected: NumberType{},
28+
},
29+
"string": {
30+
input: tftypes.String,
31+
expected: StringType{},
32+
},
33+
"dynamic": {
34+
input: tftypes.DynamicPseudoType,
35+
expected: DynamicType{},
36+
},
37+
"list": {
38+
input: tftypes.List{ElementType: tftypes.Bool},
39+
expected: ListType{ElemType: BoolType{}},
40+
},
41+
"set": {
42+
input: tftypes.Set{ElementType: tftypes.Number},
43+
expected: SetType{ElemType: NumberType{}},
44+
},
45+
"map": {
46+
input: tftypes.Map{ElementType: tftypes.String},
47+
expected: MapType{ElemType: StringType{}},
48+
},
49+
"object": {
50+
input: tftypes.Object{
51+
AttributeTypes: map[string]tftypes.Type{
52+
"bool": tftypes.Bool,
53+
"list": tftypes.List{ElementType: tftypes.Number},
54+
"nested_obj": tftypes.Object{
55+
AttributeTypes: map[string]tftypes.Type{
56+
"map": tftypes.Map{ElementType: tftypes.DynamicPseudoType},
57+
"string": tftypes.String,
58+
},
59+
},
60+
},
61+
},
62+
expected: ObjectType{
63+
AttrTypes: map[string]attr.Type{
64+
"bool": BoolType{},
65+
"list": ListType{ElemType: NumberType{}},
66+
"nested_obj": ObjectType{
67+
AttrTypes: map[string]attr.Type{
68+
"map": MapType{ElemType: DynamicType{}},
69+
"string": StringType{},
70+
},
71+
},
72+
},
73+
},
74+
},
75+
"tuple": {
76+
input: tftypes.Tuple{
77+
ElementTypes: []tftypes.Type{
78+
tftypes.Bool,
79+
tftypes.Object{
80+
AttributeTypes: map[string]tftypes.Type{
81+
"list": tftypes.List{ElementType: tftypes.DynamicPseudoType},
82+
"number": tftypes.Number,
83+
},
84+
},
85+
tftypes.Map{ElementType: tftypes.String},
86+
},
87+
},
88+
expected: TupleType{
89+
ElemTypes: []attr.Type{
90+
BoolType{},
91+
ObjectType{
92+
AttrTypes: map[string]attr.Type{
93+
"list": ListType{ElemType: DynamicType{}},
94+
"number": NumberType{},
95+
},
96+
},
97+
MapType{ElemType: StringType{}},
98+
},
99+
},
100+
},
101+
}
102+
103+
for name, test := range tests {
104+
t.Run(name, func(t *testing.T) {
105+
t.Parallel()
106+
107+
got, _ := TerraformTypeToFrameworkType(test.input)
108+
if diff := cmp.Diff(got, test.expected); diff != "" {
109+
t.Errorf("Unexpected diff (-expected, +got): %s", diff)
110+
}
111+
})
112+
}
113+
}

0 commit comments

Comments
 (0)