Skip to content

Commit e20b4f7

Browse files
committed
map
1 parent 8c6f417 commit e20b4f7

File tree

2 files changed

+98
-34
lines changed

2 files changed

+98
-34
lines changed

internal/common/autogen/unmarshal.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func setAttrTfModel(name string, field reflect.Value, val attr.Value) error {
7171
return nil
7272
}
7373

74-
func setMapAttrModel(name string, value any, mapAttrs map[string]attr.Value, mapTypes map[string]attr.Type) error {
74+
func setObjElmAttrModel(name string, value any, mapAttrs map[string]attr.Value, mapTypes map[string]attr.Type) error {
7575
nameChildTf := xstrings.ToSnakeCase(name)
7676
valueType, found := mapTypes[nameChildTf]
7777
if !found {
@@ -109,15 +109,21 @@ func getTfAttr(value any, valueType attr.Type, oldVal attr.Value, name string) (
109109
}
110110
return nil, errUnmarshal(value, valueType, "Number", nameErr)
111111
case map[string]any:
112-
obj, ok := oldVal.(types.Object)
113-
if !ok {
114-
return nil, errUnmarshal(value, valueType, "Object", nameErr)
112+
if obj, ok := oldVal.(types.Object); ok {
113+
objNew, err := setObjAttrModel(obj, v)
114+
if err != nil {
115+
return nil, err
116+
}
117+
return objNew, nil
115118
}
116-
objNew, err := setObjAttrModel(obj, v)
117-
if err != nil {
118-
return nil, err
119+
if m, ok := oldVal.(types.Map); ok {
120+
mapNew, err := setMapAttrModel(m, v)
121+
if err != nil {
122+
return nil, err
123+
}
124+
return mapNew, nil
119125
}
120-
return objNew, nil
126+
return nil, errUnmarshal(value, valueType, "Object", nameErr)
121127
case []any:
122128
if list, ok := oldVal.(types.List); ok {
123129
listNew, err := setListAttrModel(list, v, nameErr)
@@ -153,17 +159,44 @@ func setObjAttrModel(obj types.Object, objJSON map[string]any) (attr.Value, erro
153159
return nil, err
154160
}
155161
for nameChild, valueChild := range objJSON {
156-
if err := setMapAttrModel(nameChild, valueChild, mapAttrs, mapTypes); err != nil {
162+
if err := setObjElmAttrModel(nameChild, valueChild, mapAttrs, mapTypes); err != nil {
157163
return nil, err
158164
}
159165
}
160166
objNew, diags := types.ObjectValue(obj.AttributeTypes(context.Background()), mapAttrs)
161167
if diags.HasError() {
162-
return nil, fmt.Errorf("unmarshal failed to convert map to object: %v", diags)
168+
return nil, fmt.Errorf("unmarshal failed to convert JSON map to object: %v", diags)
163169
}
164170
return objNew, nil
165171
}
166172

173+
func setMapAttrModel(m types.Map, objJSON map[string]any) (attr.Value, error) {
174+
mapAttrs := m.Elements()
175+
valueType := m.ElementType(context.Background())
176+
nullVal, err := getNullAttr(valueType)
177+
if err != nil {
178+
return nil, err
179+
}
180+
for nameChild, valueChild := range objJSON {
181+
oldVal, found := mapAttrs[nameChild]
182+
if !found {
183+
oldVal = nullVal
184+
}
185+
newValue, err := getTfAttr(valueChild, valueType, oldVal, nameChild)
186+
if err != nil {
187+
return nil, err
188+
}
189+
if newValue != nil {
190+
mapAttrs[nameChild] = newValue
191+
}
192+
}
193+
mapNew, diags := types.MapValue(valueType, mapAttrs)
194+
if diags.HasError() {
195+
return nil, fmt.Errorf("unmarshal failed to convert JSON map to map: %v", diags)
196+
}
197+
return mapNew, nil
198+
}
199+
167200
func setListAttrModel(list types.List, arrayJSON []any, listName string) (attr.Value, error) {
168201
elmType := list.ElementType(context.Background())
169202
elms, err := getCollectionElements(arrayJSON, elmType, list.Elements(), listName)

internal/common/autogen/unmarshal_test.go

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
6464
AttrListListString types.List `tfsdk:"attr_list_list_string"`
6565
AttrSetListObj types.Set `tfsdk:"attr_set_list_obj"`
6666
AttrListObjKnown types.List `tfsdk:"attr_list_obj_known"`
67+
AttrMapSimple types.Map `tfsdk:"attr_map_simple"`
68+
AttrMapSimpleExisting types.Map `tfsdk:"attr_map_simple_existing"`
69+
AttrMapObj types.Map `tfsdk:"attr_map_obj"`
6770
}
6871
model := modelst{
6972
AttrObj: types.ObjectValueMust(objTypeTest.AttrTypes, map[string]attr.Value{
@@ -92,6 +95,12 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
9295
"attr_bool": types.BoolValue(true),
9396
}),
9497
}),
98+
AttrMapSimple: types.MapNull(types.StringType),
99+
AttrMapSimpleExisting: types.MapValueMust(types.StringType, map[string]attr.Value{
100+
"existing": types.StringValue("valexisting"),
101+
"existingCHANGE": types.StringValue("before"),
102+
}),
103+
AttrMapObj: types.MapUnknown(objTypeTest),
95104
}
96105
// attrUnexisting is ignored because it is in JSON but not in the model, no error is returned
97106
const (
@@ -195,7 +204,29 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
195204
"attrString": "val2",
196205
"attrInt": 2
197206
}
198-
]
207+
],
208+
"attrMapSimple": {
209+
"keyOne": "val1",
210+
"KeyTwo": "val2"
211+
},
212+
"attrMapSimpleExisting": {
213+
"key": "val",
214+
"existingCHANGE": "after"
215+
},
216+
"attrMapObj": {
217+
"obj1": {
218+
"attrString": "str1",
219+
"attrInt": 11,
220+
"attrFloat": 11.1,
221+
"attrBool": false
222+
},
223+
"obj2": {
224+
"attrString": "str2",
225+
"attrInt": 22,
226+
"attrFloat": 22.2,
227+
"attrBool": true
228+
}
229+
}
199230
}
200231
`
201232
)
@@ -321,6 +352,29 @@ func TestUnmarshalNestedAllTypes(t *testing.T) {
321352
"attr_bool": types.BoolValue(true),
322353
}),
323354
}),
355+
AttrMapSimple: types.MapValueMust(types.StringType, map[string]attr.Value{
356+
"keyOne": types.StringValue("val1"),
357+
"KeyTwo": types.StringValue("val2"), // don't change the key case when it's a map
358+
}),
359+
AttrMapSimpleExisting: types.MapValueMust(types.StringType, map[string]attr.Value{
360+
"key": types.StringValue("val"),
361+
"existing": types.StringValue("valexisting"), // existing map values are kept
362+
"existingCHANGE": types.StringValue("after"), // existing map values are changed if in JSON
363+
}),
364+
AttrMapObj: types.MapValueMust(objTypeTest, map[string]attr.Value{
365+
"obj1": types.ObjectValueMust(objTypeTest.AttrTypes, map[string]attr.Value{
366+
"attr_string": types.StringValue("str1"),
367+
"attr_int": types.Int64Value(11),
368+
"attr_float": types.Float64Value(11.1),
369+
"attr_bool": types.BoolValue(false),
370+
}),
371+
"obj2": types.ObjectValueMust(objTypeTest.AttrTypes, map[string]attr.Value{
372+
"attr_string": types.StringValue("str2"),
373+
"attr_int": types.Int64Value(22),
374+
"attr_float": types.Float64Value(22.2),
375+
"attr_bool": types.BoolValue(true),
376+
}),
377+
}),
324378
}
325379
require.NoError(t, autogen.Unmarshal([]byte(jsonResp), &model))
326380
assert.Equal(t, modelExpected, model)
@@ -476,26 +530,3 @@ func TestUnmarshalUnsupportedModel(t *testing.T) {
476530
})
477531
}
478532
}
479-
480-
// TestUnmarshalUnsupportedResponse has JSON response types not supported yet.
481-
// It will be updated when we add support for them.
482-
func TestUnmarshalUnsupportedResponse(t *testing.T) {
483-
testCases := map[string]struct {
484-
model any
485-
responseJSON string
486-
errorStr string
487-
}{
488-
"JSON maps not supported yet": {
489-
model: &struct {
490-
AttrMap types.Map `tfsdk:"attr_map"`
491-
}{},
492-
responseJSON: `{"attrMap": {"key": "value"}}`,
493-
errorStr: "unmarshal of attribute attr_map expects type MapType but got Object with value: map[key:value]",
494-
},
495-
}
496-
for name, tc := range testCases {
497-
t.Run(name, func(t *testing.T) {
498-
assert.ErrorContains(t, autogen.Unmarshal([]byte(tc.responseJSON), tc.model), tc.errorStr)
499-
})
500-
}
501-
}

0 commit comments

Comments
 (0)