Skip to content

Commit a18325b

Browse files
committed
Move customtype dir, add marshal/unmarshal tests
1 parent c21a68c commit a18325b

File tree

13 files changed

+274
-52
lines changed

13 files changed

+274
-52
lines changed

internal/common/customtype/object.go renamed to internal/common/autogen/customtype/object.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,31 @@ import (
1111
"github.com/hashicorp/terraform-plugin-go/tftypes"
1212
)
1313

14-
// Custom types: https://developer.hashicorp.com/terraform/plugin/framework/handling-data/types/custom
14+
/*
15+
Custom Object type used in auto-generated code to enable the generic marshal/unmarshal operations to access nested attribute struct tags during conversion.
16+
Custom types docs: https://developer.hashicorp.com/terraform/plugin/framework/handling-data/types/custom
17+
18+
Usage:
19+
- Schema definition:
20+
"sample_nested_object": schema.SingleNestedAttribute{
21+
...
22+
CustomType: customtype.NewObjectType[TFStreamConfigModel](ctx),
23+
Attributes: map[string]schema.Attribute{
24+
"string_attribute": schema.StringAttribute{...},
25+
},
26+
}
27+
28+
- TF Models:
29+
type TFModel struct {
30+
SampleNestedObject customtype.ObjectValue[TFSampleNestedObjectModel] `tfsdk:"sample_nested_object"`
31+
...
32+
}
33+
34+
type TFSampleNestedObjectModel struct {
35+
StringAttribute types.String `tfsdk:"string_attribute"`
36+
...
37+
}
38+
*/
1539

1640
var (
1741
_ basetypes.ObjectTypable = ObjectType[struct{}]{}
@@ -50,11 +74,11 @@ func (ObjectType[T]) String() string {
5074

5175
func (t ObjectType[T]) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
5276
if in.IsNull() {
53-
return newObjectValueNull[T](ctx), nil
77+
return NewObjectValueNull[T](ctx), nil
5478
}
5579

5680
if in.IsUnknown() {
57-
return newObjectValueUnknown[T](ctx), nil
81+
return NewObjectValueUnknown[T](ctx), nil
5882
}
5983

6084
attrTypes, diags := getAttributeTypes[T](ctx)
@@ -106,32 +130,36 @@ type ObjectValueInterface interface {
106130
}
107131

108132
func (v ObjectValue[T]) NewObjectValue(ctx context.Context, value any) ObjectValueInterface {
133+
return NewObjectValue[T](ctx, value)
134+
}
135+
136+
func NewObjectValue[T any](ctx context.Context, value any) ObjectValue[T] {
109137
attrTypes, diags := getAttributeTypes[T](ctx)
110138
if diags.HasError() {
111139
panic(fmt.Errorf("error creating ObjectValue: %v", diags))
112140
}
113141

114142
newValue, diags := basetypes.NewObjectValueFrom(ctx, attrTypes, value)
115143
if diags.HasError() {
116-
return newObjectValueUnknown[T](ctx)
144+
return NewObjectValueUnknown[T](ctx)
117145
}
118146

119147
return ObjectValue[T]{ObjectValue: newValue}
120148
}
121149

122150
func (v ObjectValue[T]) NewObjectValueNull(ctx context.Context) ObjectValueInterface {
123-
return newObjectValueNull[T](ctx)
151+
return NewObjectValueNull[T](ctx)
124152
}
125153

126-
func newObjectValueNull[T any](ctx context.Context) ObjectValue[T] {
154+
func NewObjectValueNull[T any](ctx context.Context) ObjectValue[T] {
127155
attrTypes, diags := getAttributeTypes[T](ctx)
128156
if diags.HasError() {
129157
panic(fmt.Errorf("error creating null ObjectValue: %v", diags))
130158
}
131159
return ObjectValue[T]{ObjectValue: basetypes.NewObjectNull(attrTypes)}
132160
}
133161

134-
func newObjectValueUnknown[T any](ctx context.Context) ObjectValue[T] {
162+
func NewObjectValueUnknown[T any](ctx context.Context) ObjectValue[T] {
135163
attrTypes, diags := getAttributeTypes[T](ctx)
136164
if diags.HasError() {
137165
panic(fmt.Errorf("error creating unknown ObjectValue: %v", diags))
@@ -182,7 +210,7 @@ func valueToAttributeTypes(ctx context.Context, value reflect.Value) (map[string
182210
}
183211

184212
attributeTypes := make(map[string]attr.Type)
185-
for i := 0; i < valueType.NumField(); i++ {
213+
for i := range valueType.NumField() {
186214
typeField := valueType.Field(i)
187215
valueField := value.Field(i)
188216

internal/common/autogen/marshal.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
1010
"github.com/hashicorp/terraform-plugin-framework/attr"
1111
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/customtype"
1213
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/stringcase"
13-
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/customtype"
1414
)
1515

1616
const (
@@ -45,7 +45,7 @@ func Marshal(model any, isUpdate bool) ([]byte, error) {
4545

4646
func marshalAttrs(valModel reflect.Value, isUpdate bool) (map[string]any, error) {
4747
objJSON := make(map[string]any)
48-
for i := 0; i < valModel.NumField(); i++ {
48+
for i := range valModel.NumField() {
4949
attrTypeModel := valModel.Type().Field(i)
5050
tag := attrTypeModel.Tag.Get(tagKey)
5151
if tag == tagValOmitJSON {

internal/common/autogen/marshal_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package autogen_test
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
78
"github.com/hashicorp/terraform-plugin-framework/attr"
89
"github.com/hashicorp/terraform-plugin-framework/types"
910
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen"
11+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/customtype"
1012
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
1214
)
@@ -282,6 +284,91 @@ func TestMarshalUpdateNull(t *testing.T) {
282284
assert.JSONEq(t, expectedJSONCreate, string(rawCreate))
283285
}
284286

287+
func TestMarshalCustomTypeObject(t *testing.T) {
288+
ctx := context.Background()
289+
290+
type modelEmptyTest struct{}
291+
292+
type modelCustomTypeTest struct {
293+
AttrPrimitiveOmit types.String `tfsdk:"attr_primitive_omit" autogen:"omitjson"`
294+
AttrObjectOmit customtype.ObjectValue[modelEmptyTest] `tfsdk:"attr_object_omit" autogen:"omitjson"`
295+
AttrObjectOmitUpdate customtype.ObjectValue[modelEmptyTest] `tfsdk:"attr_object_omit_update" autogen:"omitjsonupdate"`
296+
AttrNull customtype.ObjectValue[modelEmptyTest] `tfsdk:"attr_null" autogen:"includenullonupdate"`
297+
AttrInt types.Int64 `tfsdk:"attr_int"`
298+
}
299+
300+
type modelCustomTypeParentTest struct {
301+
AttrString types.String `tfsdk:"attr_string"`
302+
AttrObject customtype.ObjectValue[modelCustomTypeTest] `tfsdk:"attr_object"`
303+
}
304+
305+
nullObject := customtype.NewObjectValueNull[modelEmptyTest](ctx)
306+
emptyObject := customtype.NewObjectValue[modelEmptyTest](ctx, modelEmptyTest{})
307+
308+
model := struct {
309+
AttrObjectBasic customtype.ObjectValue[modelCustomTypeTest] `tfsdk:"attr_object_basic"`
310+
AttrObjectNull customtype.ObjectValue[modelCustomTypeTest] `tfsdk:"attr_object_null"`
311+
AttrObjectNested customtype.ObjectValue[modelCustomTypeParentTest] `tfsdk:"attr_object_nested"`
312+
}{
313+
AttrObjectBasic: customtype.NewObjectValue[modelCustomTypeTest](ctx, modelCustomTypeTest{
314+
AttrInt: types.Int64Value(1),
315+
AttrPrimitiveOmit: types.StringValue("omitted"),
316+
AttrObjectOmit: emptyObject,
317+
AttrObjectOmitUpdate: emptyObject,
318+
AttrNull: nullObject,
319+
}),
320+
AttrObjectNull: customtype.NewObjectValueNull[modelCustomTypeTest](ctx),
321+
AttrObjectNested: customtype.NewObjectValue[modelCustomTypeParentTest](ctx, modelCustomTypeParentTest{
322+
AttrString: types.StringValue("parent"),
323+
AttrObject: customtype.NewObjectValue[modelCustomTypeTest](ctx, modelCustomTypeTest{
324+
AttrInt: types.Int64Value(2),
325+
AttrPrimitiveOmit: types.StringValue("omitted"),
326+
AttrObjectOmit: emptyObject,
327+
AttrObjectOmitUpdate: emptyObject,
328+
AttrNull: nullObject,
329+
}),
330+
}),
331+
}
332+
333+
const expectedCreateJSON = `
334+
{
335+
"attrObjectBasic": {
336+
"attrInt": 1,
337+
"attrObjectOmitUpdate": {}
338+
},
339+
"attrObjectNested": {
340+
"attrObject": {
341+
"attrInt": 2,
342+
"attrObjectOmitUpdate": {}
343+
},
344+
"attrString": "parent"
345+
}
346+
}
347+
`
348+
rawCreate, err := autogen.Marshal(&model, false)
349+
require.NoError(t, err)
350+
assert.JSONEq(t, expectedCreateJSON, string(rawCreate))
351+
352+
const expectedUpdateJSON = `
353+
{
354+
"attrObjectBasic": {
355+
"attrInt": 1,
356+
"attrNull": null
357+
},
358+
"attrObjectNested": {
359+
"attrObject": {
360+
"attrInt": 2,
361+
"attrNull": null
362+
},
363+
"attrString": "parent"
364+
}
365+
}
366+
`
367+
rawUpdate, err := autogen.Marshal(&model, true)
368+
require.NoError(t, err)
369+
assert.JSONEq(t, expectedUpdateJSON, string(rawUpdate))
370+
}
371+
285372
func TestMarshalUnsupported(t *testing.T) {
286373
testCases := map[string]any{
287374
"Int32 not supported yet as it's not being used in any model": &struct {

internal/common/autogen/unknown.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
99
"github.com/hashicorp/terraform-plugin-framework/attr"
1010
"github.com/hashicorp/terraform-plugin-framework/types"
11-
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/customtype"
11+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/customtype"
1212
)
1313

1414
// ResolveUnknowns converts unknown attributes to null.
@@ -21,7 +21,7 @@ func ResolveUnknowns(model any) error {
2121
if valModel.Kind() != reflect.Struct {
2222
panic("model must be pointer to struct")
2323
}
24-
for i := 0; i < valModel.NumField(); i++ {
24+
for i := range valModel.NumField() {
2525
field := valModel.Field(i)
2626
value, ok := field.Interface().(attr.Value)
2727
if !ok || !field.CanSet() {

internal/common/autogen/unknown_test.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
package autogen_test
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/hashicorp/terraform-plugin-framework/attr"
78
"github.com/hashicorp/terraform-plugin-framework/types"
89
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen"
10+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/customtype"
911
"github.com/stretchr/testify/assert"
1012
"github.com/stretchr/testify/require"
1113
)
1214

1315
func TestResolveUnknowns(t *testing.T) {
16+
ctx := context.Background()
17+
18+
type modelEmptyTest struct{}
19+
20+
type modelCustomTypeTest struct {
21+
AttrKnownString types.String `tfsdk:"attr_known_string"`
22+
AttrUnknownObject customtype.ObjectValue[modelEmptyTest] `tfsdk:"attr_unknown_object"`
23+
}
24+
1425
type modelst struct {
15-
AttrStringUnknown types.String `tfsdk:"attr_string_unknown"`
16-
AttrObjectUnknown types.Object `tfsdk:"attr_object_unknown"`
17-
AttrListUnknown types.List `tfsdk:"attr_list_unknown"`
18-
AttrObject types.Object `tfsdk:"attr_object"`
19-
AttrListString types.List `tfsdk:"attr_list_string"`
20-
AttrSetString types.Set `tfsdk:"attr_set_string"`
21-
AttrListObjObj types.List `tfsdk:"attr_list_obj_obj"`
22-
AttrMapUnknown types.Map `tfsdk:"attr_map_unknown"`
26+
AttrStringUnknown types.String `tfsdk:"attr_string_unknown"`
27+
AttrObjectUnknown types.Object `tfsdk:"attr_object_unknown"`
28+
AttrListUnknown types.List `tfsdk:"attr_list_unknown"`
29+
AttrObject types.Object `tfsdk:"attr_object"`
30+
AttrListString types.List `tfsdk:"attr_list_string"`
31+
AttrSetString types.Set `tfsdk:"attr_set_string"`
32+
AttrListObjObj types.List `tfsdk:"attr_list_obj_obj"`
33+
AttrMapUnknown types.Map `tfsdk:"attr_map_unknown"`
34+
AttrCustomObjectUnknown customtype.ObjectValue[modelEmptyTest] `tfsdk:"attr_custom_object_unknown"`
35+
AttrCustomObject customtype.ObjectValue[modelCustomTypeTest] `tfsdk:"attr_custom_object"`
2336
}
37+
2438
model := modelst{
2539
AttrStringUnknown: types.StringUnknown(),
2640
AttrObjectUnknown: types.ObjectUnknown(objTypeTest.AttributeTypes()),
@@ -64,7 +78,12 @@ func TestResolveUnknowns(t *testing.T) {
6478
}),
6579
types.ObjectUnknown(objTypeParentTest.AttributeTypes()),
6680
}),
67-
AttrMapUnknown: types.MapUnknown(types.StringType),
81+
AttrMapUnknown: types.MapUnknown(types.StringType),
82+
AttrCustomObjectUnknown: customtype.NewObjectValueUnknown[modelEmptyTest](ctx),
83+
AttrCustomObject: customtype.NewObjectValue[modelCustomTypeTest](ctx, modelCustomTypeTest{
84+
AttrKnownString: types.StringValue("val1"),
85+
AttrUnknownObject: customtype.NewObjectValueUnknown[modelEmptyTest](ctx),
86+
}),
6887
}
6988
modelExpected := modelst{
7089
AttrStringUnknown: types.StringNull(),
@@ -109,7 +128,12 @@ func TestResolveUnknowns(t *testing.T) {
109128
}),
110129
types.ObjectNull(objTypeParentTest.AttributeTypes()),
111130
}),
112-
AttrMapUnknown: types.MapNull(types.StringType),
131+
AttrMapUnknown: types.MapNull(types.StringType),
132+
AttrCustomObjectUnknown: customtype.NewObjectValueNull[modelEmptyTest](ctx),
133+
AttrCustomObject: customtype.NewObjectValue[modelCustomTypeTest](ctx, modelCustomTypeTest{
134+
AttrKnownString: types.StringValue("val1"),
135+
AttrUnknownObject: customtype.NewObjectValueNull[modelEmptyTest](ctx),
136+
}),
113137
}
114138
require.NoError(t, autogen.ResolveUnknowns(&model))
115139
assert.Equal(t, modelExpected, model)

internal/common/autogen/unmarshal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
1111
"github.com/hashicorp/terraform-plugin-framework/attr"
1212
"github.com/hashicorp/terraform-plugin-framework/types"
13+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/customtype"
1314
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/stringcase"
14-
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/customtype"
1515
)
1616

1717
// Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model.

0 commit comments

Comments
 (0)