Skip to content

Commit d2c33cf

Browse files
committed
Support merging of response custom nested list elements into existing plan/state
1 parent 78d1a9a commit d2c33cf

File tree

7 files changed

+132
-77
lines changed

7 files changed

+132
-77
lines changed

internal/common/autogen/unknown.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,22 @@ func prepareAttr(value attr.Value) (attr.Value, error) {
7070
if v.IsUnknown() {
7171
return v.NewNestedListValueNull(ctx), nil
7272
}
73-
// If known, no need to process each list item since unmarshal does not generate unknown attributes.
74-
return v, nil
73+
74+
slicePtr, diags := v.SlicePtrAsAny(ctx)
75+
if diags.HasError() {
76+
return nil, fmt.Errorf("unmarshal failed to convert list: %v", diags)
77+
}
78+
79+
sliceVal := reflect.ValueOf(slicePtr).Elem()
80+
for i := range sliceVal.Len() {
81+
elementPtr := sliceVal.Index(i).Addr().Interface()
82+
err := ResolveUnknowns(elementPtr)
83+
if err != nil {
84+
return nil, err
85+
}
86+
}
87+
88+
return v.NewNestedListValue(ctx, slicePtr), nil
7589
case customtypes.NestedSetValueInterface:
7690
if v.IsUnknown() {
7791
return v.NewNestedSetValueNull(ctx), nil

internal/common/autogen/unknown_test.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,21 @@ func TestResolveUnknowns(t *testing.T) {
2424
}
2525

2626
type modelst struct {
27-
AttrStringUnknown types.String `tfsdk:"attr_string_unknown"`
28-
AttrObjectUnknown types.Object `tfsdk:"attr_object_unknown"`
29-
AttrListUnknown types.List `tfsdk:"attr_list_unknown"`
30-
AttrObject types.Object `tfsdk:"attr_object"`
31-
AttrListString types.List `tfsdk:"attr_list_string"`
32-
AttrSetString types.Set `tfsdk:"attr_set_string"`
33-
AttrListObjObj types.List `tfsdk:"attr_list_obj_obj"`
34-
AttrMapUnknown types.Map `tfsdk:"attr_map_unknown"`
35-
AttrCustomObjectUnknown customtypes.ObjectValue[modelEmptyTest] `tfsdk:"attr_custom_object_unknown"`
36-
AttrCustomObject customtypes.ObjectValue[modelCustomTypeTest] `tfsdk:"attr_custom_object"`
37-
AttrCustomListUnknown customtypes.ListValue[types.String] `tfsdk:"attr_custom_list_string"`
38-
AttrCustomNestedListUnknown customtypes.NestedListValue[modelEmptyTest] `tfsdk:"attr_custom_nested_list_unknown"`
39-
AttrCustomNestedSetUnknown customtypes.NestedSetValue[modelEmptyTest] `tfsdk:"attr_custom_nested_set_unknown"`
27+
AttrStringUnknown types.String `tfsdk:"attr_string_unknown"`
28+
AttrObjectUnknown types.Object `tfsdk:"attr_object_unknown"`
29+
AttrListUnknown types.List `tfsdk:"attr_list_unknown"`
30+
AttrObject types.Object `tfsdk:"attr_object"`
31+
AttrListString types.List `tfsdk:"attr_list_string"`
32+
AttrSetString types.Set `tfsdk:"attr_set_string"`
33+
AttrListObjObj types.List `tfsdk:"attr_list_obj_obj"`
34+
AttrMapUnknown types.Map `tfsdk:"attr_map_unknown"`
35+
AttrCustomObjectUnknown customtypes.ObjectValue[modelEmptyTest] `tfsdk:"attr_custom_object_unknown"`
36+
AttrCustomObject customtypes.ObjectValue[modelCustomTypeTest] `tfsdk:"attr_custom_object"`
37+
AttrCustomListUnknown customtypes.ListValue[types.String] `tfsdk:"attr_custom_list_string"`
38+
AttrCustomNestedListUnknown customtypes.NestedListValue[modelEmptyTest] `tfsdk:"attr_custom_nested_list_unknown"`
39+
AttrCustomNestedSetUnknown customtypes.NestedSetValue[modelEmptyTest] `tfsdk:"attr_custom_nested_set_unknown"`
40+
AttrCustomNestedList customtypes.NestedListValue[modelCustomTypeTest] `tfsdk:"attr_custom_nested_list"`
41+
AttrCustomNestedSet customtypes.NestedSetValue[modelCustomTypeTest] `tfsdk:"attr_custom_nested_set"`
4042
}
4143

4244
model := modelst{
@@ -92,6 +94,18 @@ func TestResolveUnknowns(t *testing.T) {
9294
AttrCustomNestedListUnknown: customtypes.NewNestedListValueUnknown[modelEmptyTest](ctx),
9395
AttrCustomNestedSetUnknown: customtypes.NewNestedSetValueUnknown[modelEmptyTest](ctx),
9496
AttrCustomListUnknown: customtypes.NewListValueUnknown[types.String](ctx),
97+
AttrCustomNestedList: customtypes.NewNestedListValue[modelCustomTypeTest](ctx, []modelCustomTypeTest{
98+
{
99+
AttrKnownString: types.StringValue("val1"),
100+
AttrUnknownObject: customtypes.NewObjectValueUnknown[modelEmptyTest](ctx),
101+
AttrMANYUpper: types.Int64Unknown(),
102+
},
103+
{
104+
AttrKnownString: types.StringUnknown(),
105+
AttrUnknownObject: customtypes.NewObjectValue[modelEmptyTest](ctx, modelEmptyTest{}),
106+
AttrMANYUpper: types.Int64Value(2),
107+
},
108+
}),
95109
}
96110
modelExpected := modelst{
97111
AttrStringUnknown: types.StringNull(),
@@ -146,6 +160,18 @@ func TestResolveUnknowns(t *testing.T) {
146160
AttrCustomListUnknown: customtypes.NewListValueNull[types.String](ctx),
147161
AttrCustomNestedListUnknown: customtypes.NewNestedListValueNull[modelEmptyTest](ctx),
148162
AttrCustomNestedSetUnknown: customtypes.NewNestedSetValueNull[modelEmptyTest](ctx),
163+
AttrCustomNestedList: customtypes.NewNestedListValue[modelCustomTypeTest](ctx, []modelCustomTypeTest{
164+
{
165+
AttrKnownString: types.StringValue("val1"),
166+
AttrUnknownObject: customtypes.NewObjectValueNull[modelEmptyTest](ctx),
167+
AttrMANYUpper: types.Int64Null(),
168+
},
169+
{
170+
AttrKnownString: types.StringNull(),
171+
AttrUnknownObject: customtypes.NewObjectValue[modelEmptyTest](ctx, modelEmptyTest{}),
172+
AttrMANYUpper: types.Int64Value(2),
173+
},
174+
}),
149175
}
150176
require.NoError(t, autogen.ResolveUnknowns(&model))
151177
assert.Equal(t, modelExpected, model)

internal/common/autogen/unmarshal.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -327,16 +327,37 @@ func getListValueTFAttr(ctx context.Context, arrayJSON []any, list customtypes.L
327327
}
328328

329329
func getNestedListValueTFAttr(ctx context.Context, arrayJSON []any, list customtypes.NestedListValueInterface) (attr.Value, error) {
330-
if len(arrayJSON) == 0 && list.Len() == 0 {
330+
oldSlicePtr, diags := list.SlicePtrAsAny(ctx)
331+
if diags.HasError() {
332+
return nil, fmt.Errorf("unmarshal failed to convert list: %v", diags)
333+
}
334+
oldSliceVal := reflect.ValueOf(oldSlicePtr).Elem()
335+
oldSliceLen := oldSliceVal.Len()
336+
337+
if len(arrayJSON) == 0 && oldSliceLen == 0 {
331338
// Keep current list if both model and JSON lists are zero-len (empty or null) so config is preserved.
332339
// It avoids inconsistent result after apply when user explicitly sets an empty list in config.
333340
return list, nil
334341
}
335342

336343
slicePtr := list.NewEmptySlicePtr()
337-
err := unmarshalNestedSlice(arrayJSON, slicePtr)
338-
if err != nil {
339-
return nil, err
344+
sliceVal := reflect.ValueOf(slicePtr).Elem()
345+
sliceVal.Set(reflect.MakeSlice(sliceVal.Type(), len(arrayJSON), len(arrayJSON)))
346+
347+
for i, item := range arrayJSON {
348+
elementVal := sliceVal.Index(i)
349+
if i < oldSliceLen {
350+
elementVal.Set(oldSliceVal.Index(i))
351+
}
352+
elementPtr := elementVal.Addr().Interface()
353+
objJSON, ok := item.(map[string]any)
354+
if !ok {
355+
return nil, fmt.Errorf("unmarshal of array item failed to convert to object: %v", item)
356+
}
357+
err := unmarshalAttrs(objJSON, elementPtr)
358+
if err != nil {
359+
return nil, err
360+
}
340361
}
341362

342363
return list.NewNestedListValue(ctx, slicePtr), nil
@@ -350,29 +371,20 @@ func getNestedSetValueTFAttr(ctx context.Context, arrayJSON []any, set customtyp
350371
}
351372

352373
slicePtr := set.NewEmptySlicePtr()
353-
err := unmarshalNestedSlice(arrayJSON, slicePtr)
354-
if err != nil {
355-
return nil, err
356-
}
357-
358-
return set.NewNestedSetValue(ctx, slicePtr), nil
359-
}
360-
361-
func unmarshalNestedSlice(arrayJSON []any, slicePtr any) error {
362374
sliceVal := reflect.ValueOf(slicePtr).Elem()
363375
sliceVal.Set(reflect.MakeSlice(sliceVal.Type(), len(arrayJSON), len(arrayJSON)))
364376

365377
for i, item := range arrayJSON {
366378
elementPtr := sliceVal.Index(i).Addr().Interface()
367379
objJSON, ok := item.(map[string]any)
368380
if !ok {
369-
return fmt.Errorf("unmarshal of array item failed to convert to object: %v", item)
381+
return nil, fmt.Errorf("unmarshal of array item failed to convert to object: %v", item)
370382
}
371383
err := unmarshalAttrs(objJSON, elementPtr)
372384
if err != nil {
373-
return err
385+
return nil, err
374386
}
375387
}
376388

377-
return nil
389+
return set.NewNestedSetValue(ctx, slicePtr), nil
378390
}

internal/common/autogen/unmarshal_test.go

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
7373
AttrMANYUpper types.Int64 `tfsdk:"attr_many_upper"`
7474
}
7575

76+
modelCustomTypeBasic := modelCustomTypeTest{
77+
AttrString: types.StringValue("different_string"),
78+
AttrInt: types.Int64Value(999),
79+
AttrFloat: types.Float64Unknown(),
80+
AttrBool: types.BoolUnknown(),
81+
AttrNested: customtypes.NewObjectValueUnknown[modelEmptyTest](ctx),
82+
AttrMANYUpper: types.Int64Value(999),
83+
}
84+
7685
type modelst struct {
7786
AttrObj types.Object `tfsdk:"attr_obj"`
7887
AttrObjNullNotSent types.Object `tfsdk:"attr_obj_null_not_sent"`
@@ -117,20 +126,12 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
117126
"attr_float": types.Float64Unknown(), // can even be null
118127
"attr_bool": types.BoolUnknown(), // can even be unknown
119128
}),
120-
AttrObjNullNotSent: types.ObjectNull(objTypeTest.AttrTypes),
121-
AttrObjNullSent: types.ObjectNull(objTypeTest.AttrTypes),
122-
AttrObjUnknownNotSent: types.ObjectUnknown(objTypeTest.AttrTypes), // unknown values are changed to null
123-
AttrObjUnknownSent: types.ObjectUnknown(objTypeTest.AttrTypes),
124-
AttrObjParent: types.ObjectNull(objTypeParentTest.AttrTypes),
125-
AttrCustomObj: customtypes.NewObjectValue[modelCustomTypeTest](ctx, modelCustomTypeTest{
126-
// these attribute values are irrelevant, they will be overwritten with JSON values
127-
AttrString: types.StringValue("different_string"),
128-
AttrInt: types.Int64Value(999),
129-
AttrFloat: types.Float64Unknown(), // can even be unknown
130-
AttrBool: types.BoolUnknown(), // can even be unknown
131-
AttrNested: customtypes.NewObjectValueUnknown[modelEmptyTest](ctx),
132-
AttrMANYUpper: types.Int64Value(999),
133-
}),
129+
AttrObjNullNotSent: types.ObjectNull(objTypeTest.AttrTypes),
130+
AttrObjNullSent: types.ObjectNull(objTypeTest.AttrTypes),
131+
AttrObjUnknownNotSent: types.ObjectUnknown(objTypeTest.AttrTypes), // unknown values are changed to null
132+
AttrObjUnknownSent: types.ObjectUnknown(objTypeTest.AttrTypes),
133+
AttrObjParent: types.ObjectNull(objTypeParentTest.AttrTypes),
134+
AttrCustomObj: customtypes.NewObjectValue[modelCustomTypeTest](ctx, modelCustomTypeBasic),
134135
AttrCustomObjNullNotSent: customtypes.NewObjectValueNull[modelCustomTypeTest](ctx),
135136
AttrCustomObjNullSent: customtypes.NewObjectValueNull[modelCustomTypeTest](ctx),
136137
AttrCustomObjUnknownNotSent: customtypes.NewObjectValueUnknown[modelCustomTypeTest](ctx), // unknown values are changed to null
@@ -140,10 +141,10 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
140141
AttrCustomListString: customtypes.NewListValueUnknown[types.String](ctx),
141142
AttrListObj: types.ListUnknown(objTypeTest),
142143
AttrCustomNestedList: customtypes.NewNestedListValue[modelCustomTypeTest](ctx, []modelCustomTypeTest{
144+
modelCustomTypeBasic,
143145
{
144-
// these attribute values are irrelevant, they will be overwritten with JSON values
145-
AttrString: types.StringValue("different_string"),
146-
AttrInt: types.Int64Value(999),
146+
AttrString: types.StringValue("existing not overwritten"),
147+
AttrInt: types.Int64Unknown(),
147148
AttrFloat: types.Float64Unknown(),
148149
AttrBool: types.BoolUnknown(),
149150
AttrNested: customtypes.NewObjectValueUnknown[modelEmptyTest](ctx),
@@ -156,23 +157,13 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
156157
AttrCustomNestedListUnknownSent: customtypes.NewNestedListValueUnknown[modelCustomTypeTest](ctx),
157158
AttrSetString: types.SetUnknown(types.StringType),
158159
AttrSetObj: types.SetUnknown(objTypeTest),
159-
AttrCustomNestedSet: customtypes.NewNestedSetValue[modelCustomTypeTest](ctx, []modelCustomTypeTest{
160-
{
161-
// these attribute values are irrelevant, they will be overwritten with JSON values
162-
AttrString: types.StringValue("different_string"),
163-
AttrInt: types.Int64Value(999),
164-
AttrFloat: types.Float64Unknown(),
165-
AttrBool: types.BoolUnknown(),
166-
AttrNested: customtypes.NewObjectValueUnknown[modelEmptyTest](ctx),
167-
AttrMANYUpper: types.Int64Value(999),
168-
},
169-
}),
170-
AttrCustomNestedSetNullNotSent: customtypes.NewNestedSetValueNull[modelCustomTypeTest](ctx),
171-
AttrCustomNestedSetNullSent: customtypes.NewNestedSetValueNull[modelCustomTypeTest](ctx),
172-
AttrCustomNestedSetUnknownNotSent: customtypes.NewNestedSetValueUnknown[modelCustomTypeTest](ctx),
173-
AttrCustomNestedSetUnknownSent: customtypes.NewNestedSetValueUnknown[modelCustomTypeTest](ctx),
174-
AttrListListString: types.ListUnknown(types.ListType{ElemType: types.StringType}),
175-
AttrSetListObj: types.SetUnknown(types.ListType{ElemType: objTypeTest}),
160+
AttrCustomNestedSet: customtypes.NewNestedSetValue[modelCustomTypeTest](ctx, []modelCustomTypeTest{modelCustomTypeBasic}),
161+
AttrCustomNestedSetNullNotSent: customtypes.NewNestedSetValueNull[modelCustomTypeTest](ctx),
162+
AttrCustomNestedSetNullSent: customtypes.NewNestedSetValueNull[modelCustomTypeTest](ctx),
163+
AttrCustomNestedSetUnknownNotSent: customtypes.NewNestedSetValueUnknown[modelCustomTypeTest](ctx),
164+
AttrCustomNestedSetUnknownSent: customtypes.NewNestedSetValueUnknown[modelCustomTypeTest](ctx),
165+
AttrListListString: types.ListUnknown(types.ListType{ElemType: types.StringType}),
166+
AttrSetListObj: types.SetUnknown(types.ListType{ElemType: objTypeTest}),
176167
AttrListObjKnown: types.ListValueMust(objTypeTest, []attr.Value{
177168
types.ObjectValueMust(objTypeTest.AttrTypes, map[string]attr.Value{
178169
"attr_string": types.StringValue("val"),
@@ -266,8 +257,6 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
266257
"attrMANYUpper": 123
267258
},
268259
{
269-
"attrString": "nestedList2",
270-
"attrInt": 2,
271260
"attrFloat": 2.2,
272261
"attrBool": false,
273262
"attrNested": {},
@@ -490,8 +479,8 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
490479
AttrMANYUpper: types.Int64Value(123),
491480
},
492481
{
493-
AttrString: types.StringValue("nestedList2"),
494-
AttrInt: types.Int64Value(2),
482+
AttrString: types.StringValue("existing not overwritten"),
483+
AttrInt: types.Int64Unknown(),
495484
AttrFloat: types.Float64Value(2.2),
496485
AttrBool: types.BoolValue(false),
497486
AttrNested: customtypes.NewObjectValue[modelEmptyTest](ctx, modelEmptyTest{}),

internal/serviceapi/customdbroleapi/resource_schema.go

Lines changed: 10 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/codegen/config.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,6 @@ resources:
231231
path: /api/atlas/v2/groups/{groupId}/customDBRoles/roles/{roleName}
232232
method: DELETE
233233
version_header: application/vnd.atlas.2023-01-01+json
234-
schema:
235-
use_custom_nested_types: false
236234

237235
database_user_api:
238236
read:

tools/codegen/models/custom_db_role_api.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,21 @@ schema:
4040
sensitive: false
4141
create_only: false
4242
description: List of resources on which you grant the action.
43+
custom_type:
44+
package: customtypes
45+
model: customtypes.NestedListValue[TFActionsResourcesModel]
46+
schema: customtypes.NewNestedListType[TFActionsResourcesModel](ctx)
4347
computed_optional_required: optional
4448
tf_schema_name: resources
4549
tf_model_name: Resources
4650
req_body_usage: all_request_bodies
4751
sensitive: false
4852
create_only: false
4953
description: List of the individual privilege actions that the role grants.
54+
custom_type:
55+
package: customtypes
56+
model: customtypes.NestedListValue[TFActionsModel]
57+
schema: customtypes.NewNestedListType[TFActionsModel](ctx)
5058
computed_optional_required: optional
5159
tf_schema_name: actions
5260
tf_model_name: Actions
@@ -84,6 +92,10 @@ schema:
8492
sensitive: false
8593
create_only: false
8694
description: List of the built-in roles that this custom role inherits.
95+
custom_type:
96+
package: customtypes
97+
model: customtypes.NestedSetValue[TFInheritedRolesModel]
98+
schema: customtypes.NewNestedSetType[TFInheritedRolesModel](ctx)
8799
computed_optional_required: optional
88100
tf_schema_name: inherited_roles
89101
tf_model_name: InheritedRoles

0 commit comments

Comments
 (0)