Skip to content

Commit 603f19d

Browse files
chore: Support dynamic json values which Map type (#3860)
* improve original error message * adding failing test in api spec parsing logic * fix parsing of map type fixing leaving unit test passing * code generation working * add marshal/unmarshal unit tests * add acceptance test
1 parent d009f3a commit 603f19d

File tree

15 files changed

+447
-83
lines changed

15 files changed

+447
-83
lines changed

internal/common/autogen/marshal_test.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,49 @@ func TestMarshalBasic(t *testing.T) {
5656

5757
func TestMarshalDynamicJSONAttr(t *testing.T) {
5858
model := struct {
59-
AttrDynamicJSONObject jsontypes.Normalized `tfsdk:"attr_dynamic_json_object"`
60-
AttrDynamicJSONBoolean jsontypes.Normalized `tfsdk:"attr_dynamic_json_boolean"`
61-
AttrDynamicJSONString jsontypes.Normalized `tfsdk:"attr_dynamic_json_string"`
62-
AttrDynamicJSONNumber jsontypes.Normalized `tfsdk:"attr_dynamic_json_number"`
63-
AttrDynamicJSONArray jsontypes.Normalized `tfsdk:"attr_dynamic_json_array"`
59+
AttrDynamicJSONObject jsontypes.Normalized `tfsdk:"attr_dynamic_json_object"`
60+
AttrDynamicJSONBoolean jsontypes.Normalized `tfsdk:"attr_dynamic_json_boolean"`
61+
AttrDynamicJSONString jsontypes.Normalized `tfsdk:"attr_dynamic_json_string"`
62+
AttrDynamicJSONNumber jsontypes.Normalized `tfsdk:"attr_dynamic_json_number"`
63+
AttrDynamicJSONArray jsontypes.Normalized `tfsdk:"attr_dynamic_json_array"`
64+
AttrListOfDynamicJSONObjects customtypes.ListValue[jsontypes.Normalized] `tfsdk:"attr_list_of_dynamic_json_objects"`
65+
AttrSetOfDynamicJSONObjects customtypes.SetValue[jsontypes.Normalized] `tfsdk:"attr_set_of_dynamic_json_objects"`
66+
AttrMapOfDynamicJSONObjects customtypes.MapValue[jsontypes.Normalized] `tfsdk:"attr_map_of_dynamic_json_objects"`
67+
AttrListOfDynamicJSONBooleans customtypes.ListValue[jsontypes.Normalized] `tfsdk:"attr_list_of_dynamic_json_booleans"`
68+
AttrSetOfDynamicJSONBooleans customtypes.SetValue[jsontypes.Normalized] `tfsdk:"attr_set_of_dynamic_json_booleans"`
69+
AttrMapOfDynamicJSONBooleans customtypes.MapValue[jsontypes.Normalized] `tfsdk:"attr_map_of_dynamic_json_booleans"`
6470
}{
65-
AttrDynamicJSONObject: jsontypes.NewNormalizedValue("{\"hello\": \"there\"}"),
66-
AttrDynamicJSONBoolean: jsontypes.NewNormalizedValue("true"),
67-
AttrDynamicJSONString: jsontypes.NewNormalizedValue("\"hello\""),
68-
AttrDynamicJSONNumber: jsontypes.NewNormalizedValue("1.234"),
69-
AttrDynamicJSONArray: jsontypes.NewNormalizedValue("[1, 2, 3]"),
71+
AttrDynamicJSONObject: jsontypes.NewNormalizedValue("{\"hello\": \"there\"}"),
72+
AttrDynamicJSONBoolean: jsontypes.NewNormalizedValue("true"),
73+
AttrDynamicJSONString: jsontypes.NewNormalizedValue("\"hello\""),
74+
AttrDynamicJSONNumber: jsontypes.NewNormalizedValue("1.234"),
75+
AttrDynamicJSONArray: jsontypes.NewNormalizedValue("[1, 2, 3]"),
76+
AttrListOfDynamicJSONObjects: customtypes.NewListValue[jsontypes.Normalized](t.Context(), []attr.Value{jsontypes.NewNormalizedValue("{\"hello\": \"there\"}")}),
77+
AttrSetOfDynamicJSONObjects: customtypes.NewSetValue[jsontypes.Normalized](t.Context(), []attr.Value{jsontypes.NewNormalizedValue("{\"hello\": \"there\"}")}),
78+
AttrMapOfDynamicJSONObjects: customtypes.NewMapValue[jsontypes.Normalized](t.Context(), map[string]attr.Value{
79+
"key1": jsontypes.NewNormalizedValue("{\"hello\": \"there\"}"),
80+
"key2": jsontypes.NewNormalizedValue("{\"hello\": \"there\"}"),
81+
}),
82+
AttrListOfDynamicJSONBooleans: customtypes.NewListValue[jsontypes.Normalized](t.Context(), []attr.Value{jsontypes.NewNormalizedValue("true")}),
83+
AttrSetOfDynamicJSONBooleans: customtypes.NewSetValue[jsontypes.Normalized](t.Context(), []attr.Value{jsontypes.NewNormalizedValue("true")}),
84+
AttrMapOfDynamicJSONBooleans: customtypes.NewMapValue[jsontypes.Normalized](t.Context(), map[string]attr.Value{
85+
"key1": jsontypes.NewNormalizedValue("true"),
86+
"key2": jsontypes.NewNormalizedValue("false"),
87+
}),
7088
}
7189
const expectedJSON = `
7290
{
7391
"attrDynamicJSONObject": {"hello": "there"},
7492
"attrDynamicJSONBoolean": true,
7593
"attrDynamicJSONString": "hello",
7694
"attrDynamicJSONNumber": 1.234,
77-
"attrDynamicJSONArray": [1, 2, 3]
95+
"attrDynamicJSONArray": [1, 2, 3],
96+
"attrListOfDynamicJSONObjects": [{"hello": "there"}],
97+
"attrSetOfDynamicJSONObjects": [{"hello": "there"}],
98+
"attrMapOfDynamicJSONObjects": {"key1": {"hello": "there"}, "key2": {"hello": "there"}},
99+
"attrListOfDynamicJSONBooleans": [true],
100+
"attrSetOfDynamicJSONBooleans": [true],
101+
"attrMapOfDynamicJSONBooleans": {"key1": true, "key2": false}
78102
}
79103
`
80104
raw, err := autogen.Marshal(&model, false)

internal/common/autogen/unmarshal_test.go

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,68 @@ func TestUnmarshalBasic(t *testing.T) {
5858
}
5959

6060
func TestUnmarshalDynamicJSONAttr(t *testing.T) {
61-
var model struct {
62-
AttrDynamicJSONObject jsontypes.Normalized `tfsdk:"attr_dynamic_json_object"`
63-
AttrDynamicJSONBoolean jsontypes.Normalized `tfsdk:"attr_dynamic_json_boolean"`
64-
AttrDynamicJSONNumber jsontypes.Normalized `tfsdk:"attr_dynamic_json_number"`
65-
AttrDynamicJSONString jsontypes.Normalized `tfsdk:"attr_dynamic_json_string"`
66-
AttrDynamicJSONArray jsontypes.Normalized `tfsdk:"attr_dynamic_json_array"`
61+
ctx := context.Background()
62+
63+
type modelst struct {
64+
AttrDynamicJSONObject jsontypes.Normalized `tfsdk:"attr_dynamic_json_object"`
65+
AttrDynamicJSONBoolean jsontypes.Normalized `tfsdk:"attr_dynamic_json_boolean"`
66+
AttrDynamicJSONNumber jsontypes.Normalized `tfsdk:"attr_dynamic_json_number"`
67+
AttrDynamicJSONString jsontypes.Normalized `tfsdk:"attr_dynamic_json_string"`
68+
AttrDynamicJSONArray jsontypes.Normalized `tfsdk:"attr_dynamic_json_array"`
69+
AttrListOfDynamicJSONObjects customtypes.ListValue[jsontypes.Normalized] `tfsdk:"attr_list_of_dynamic_json_objects"`
70+
AttrSetOfDynamicJSONObjects customtypes.SetValue[jsontypes.Normalized] `tfsdk:"attr_set_of_dynamic_json_objects"`
71+
AttrMapOfDynamicJSONObjects customtypes.MapValue[jsontypes.Normalized] `tfsdk:"attr_map_of_dynamic_json_objects"`
72+
AttrListOfDynamicJSONBooleans customtypes.ListValue[jsontypes.Normalized] `tfsdk:"attr_list_of_dynamic_json_booleans"`
73+
AttrSetOfDynamicJSONBooleans customtypes.SetValue[jsontypes.Normalized] `tfsdk:"attr_set_of_dynamic_json_booleans"`
74+
AttrMapOfDynamicJSONBooleans customtypes.MapValue[jsontypes.Normalized] `tfsdk:"attr_map_of_dynamic_json_booleans"`
6775
}
76+
77+
var model modelst
6878
const jsonResp = `
6979
{
7080
"attrDynamicJSONObject": {"hello":"there"},
7181
"attrDynamicJSONBoolean": true,
7282
"attrDynamicJSONNumber": 1.234,
7383
"attrDynamicJSONString": "hello",
74-
"attrDynamicJSONArray": [1, 2, 3]
84+
"attrDynamicJSONArray": [1, 2, 3],
85+
"attrListOfDynamicJSONObjects": [{"hello": "there"}],
86+
"attrSetOfDynamicJSONObjects": [{"hello": "there"}],
87+
"attrMapOfDynamicJSONObjects": {"key1": {"hello": "there"}, "key2": {"hello": "there"}},
88+
"attrListOfDynamicJSONBooleans": [true],
89+
"attrSetOfDynamicJSONBooleans": [true],
90+
"attrMapOfDynamicJSONBooleans": {"key1": true, "key2": false}
7591
}
7692
`
93+
modelExpected := modelst{
94+
AttrDynamicJSONObject: jsontypes.NewNormalizedValue("{\"hello\":\"there\"}"),
95+
AttrDynamicJSONBoolean: jsontypes.NewNormalizedValue("true"),
96+
AttrDynamicJSONNumber: jsontypes.NewNormalizedValue("1.234"),
97+
AttrDynamicJSONString: jsontypes.NewNormalizedValue("\"hello\""),
98+
AttrDynamicJSONArray: jsontypes.NewNormalizedValue("[1,2,3]"),
99+
AttrListOfDynamicJSONObjects: customtypes.NewListValue[jsontypes.Normalized](ctx, []attr.Value{
100+
jsontypes.NewNormalizedValue("{\"hello\":\"there\"}"),
101+
}),
102+
AttrSetOfDynamicJSONObjects: customtypes.NewSetValue[jsontypes.Normalized](ctx, []attr.Value{
103+
jsontypes.NewNormalizedValue("{\"hello\":\"there\"}"),
104+
}),
105+
AttrMapOfDynamicJSONObjects: customtypes.NewMapValue[jsontypes.Normalized](ctx, map[string]attr.Value{
106+
"key1": jsontypes.NewNormalizedValue("{\"hello\":\"there\"}"),
107+
"key2": jsontypes.NewNormalizedValue("{\"hello\":\"there\"}"),
108+
}),
109+
AttrListOfDynamicJSONBooleans: customtypes.NewListValue[jsontypes.Normalized](ctx, []attr.Value{
110+
jsontypes.NewNormalizedValue("true"),
111+
}),
112+
AttrSetOfDynamicJSONBooleans: customtypes.NewSetValue[jsontypes.Normalized](ctx, []attr.Value{
113+
jsontypes.NewNormalizedValue("true"),
114+
}),
115+
AttrMapOfDynamicJSONBooleans: customtypes.NewMapValue[jsontypes.Normalized](ctx, map[string]attr.Value{
116+
"key1": jsontypes.NewNormalizedValue("true"),
117+
"key2": jsontypes.NewNormalizedValue("false"),
118+
}),
119+
}
120+
77121
require.NoError(t, autogen.Unmarshal([]byte(jsonResp), &model))
78-
assert.JSONEq(t, "{\"hello\":\"there\"}", model.AttrDynamicJSONObject.ValueString())
79-
assert.JSONEq(t, "true", model.AttrDynamicJSONBoolean.ValueString())
80-
assert.JSONEq(t, "1.234", model.AttrDynamicJSONNumber.ValueString())
81-
assert.JSONEq(t, "\"hello\"", model.AttrDynamicJSONString.ValueString())
82-
assert.JSONEq(t, "[1, 2, 3]", model.AttrDynamicJSONArray.ValueString())
122+
assert.Equal(t, modelExpected, model)
83123
}
84124

85125
func TestUnmarshalSensitiveAttribute(t *testing.T) {

internal/serviceapi/searchindexapi/resource_schema.go

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

internal/serviceapi/searchindexapi/resource_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,29 @@ func TestAccSearchIndexAPI_basic(t *testing.T) {
4343
})
4444
}
4545

46+
func TestAccSearchIndexAPI_withMappingsFields(t *testing.T) {
47+
var (
48+
projectID, clusterName = acc.ClusterNameExecution(t, true)
49+
indexName = acc.RandomName()
50+
)
51+
resource.ParallelTest(t, resource.TestCase{
52+
PreCheck: func() { acc.PreCheckBasic(t) },
53+
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
54+
CheckDestroy: checkDestroy,
55+
Steps: []resource.TestStep{
56+
{
57+
Config: configWithMappingsFields(projectID, clusterName, indexName, true),
58+
Check: checkWithMappingsFields(projectID, clusterName, indexName, true),
59+
},
60+
// TODO: revise update behavior as part of CLOUDP-352324
61+
// {
62+
// Config: configWithMappingsFields(projectID, clusterName, indexName, false),
63+
// Check: checkWithMappingsFields(projectID, clusterName, indexName, false),
64+
// },
65+
},
66+
})
67+
}
68+
4669
func configBasic(projectID, clusterName, indexName string) string {
4770
return fmt.Sprintf(`
4871
resource "mongodbatlas_search_index_api" "test" {
@@ -61,6 +84,59 @@ func configBasic(projectID, clusterName, indexName string) string {
6184
`, projectID, clusterName, indexName, database, collection)
6285
}
6386

87+
func configWithMappingsFields(projectID, clusterName, indexName string, with bool) string {
88+
var fields string
89+
if with {
90+
fields = `
91+
fields = {
92+
address = jsonencode({
93+
type = "document"
94+
fields = {
95+
city = {
96+
type = "string"
97+
analyzer = "lucene.simple"
98+
ignoreAbove = 255
99+
}
100+
state = {
101+
type = "string"
102+
analyzer = "lucene.english"
103+
}
104+
}
105+
})
106+
company = jsonencode({
107+
type = "string"
108+
analyzer = "lucene.whitespace"
109+
multi = {
110+
mySecondaryAnalyzer = {
111+
type = "string"
112+
analyzer = "lucene.french"
113+
}
114+
}
115+
})
116+
employees = jsonencode({
117+
type = "string"
118+
analyzer = "lucene.standard"
119+
})
120+
}`
121+
}
122+
return fmt.Sprintf(`
123+
resource "mongodbatlas_search_index_api" "test" {
124+
group_id = %[1]q
125+
cluster_name = %[2]q
126+
name = %[3]q
127+
database = %[4]q
128+
collection_name = %[5]q
129+
130+
definition = {
131+
mappings = {
132+
dynamic = jsonencode(true)
133+
%[6]s
134+
}
135+
}
136+
}
137+
`, projectID, clusterName, indexName, database, collection, fields)
138+
}
139+
64140
func checkBasic(projectID, clusterName, indexName string) resource.TestCheckFunc {
65141
attributes := map[string]string{
66142
"group_id": projectID,
@@ -78,6 +154,17 @@ func checkBasic(projectID, clusterName, indexName string) resource.TestCheckFunc
78154
return resource.ComposeAggregateTestCheckFunc(checks...)
79155
}
80156

157+
func checkWithMappingsFields(projectID, clusterName, indexName string, has bool) resource.TestCheckFunc {
158+
count := "0"
159+
if has {
160+
count = "3"
161+
}
162+
return resource.ComposeAggregateTestCheckFunc(
163+
checkBasic(projectID, clusterName, indexName),
164+
resource.TestCheckResourceAttr(resourceName, "latest_definition.mappings.fields.%", count),
165+
)
166+
}
167+
81168
func checkExists(resourceName string) resource.TestCheckFunc {
82169
return func(s *terraform.State) error {
83170
rs, ok := s.RootModule().Resources[resourceName]

tools/codegen/codespec/api_to_provider_spec_mapper_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,77 @@ func TestConvertToProviderSpec_typeOverride(t *testing.T) {
720720
runTestCase(t, tc)
721721
}
722722

723+
func TestConvertToProviderSpec_dynamicJSONProperties(t *testing.T) {
724+
tc := convertToSpecTestCase{
725+
inputOpenAPISpecPath: testDataAPISpecPath,
726+
inputConfigPath: testDataConfigPath,
727+
inputResourceName: "test_dynamic_json_properties",
728+
729+
expectedResult: &codespec.Model{
730+
Resources: []codespec.Resource{{
731+
Schema: &codespec.Schema{
732+
Description: conversion.StringPtr(testResourceDesc),
733+
Attributes: codespec.Attributes{
734+
{
735+
TFSchemaName: "array_of_dynamic_values",
736+
TFModelName: "ArrayOfDynamicValues",
737+
ComputedOptionalRequired: codespec.Optional,
738+
CustomType: codespec.NewCustomListType(codespec.CustomTypeJSON),
739+
List: &codespec.ListAttribute{
740+
ElementType: codespec.CustomTypeJSON,
741+
},
742+
Description: conversion.StringPtr("Array of dynamic values."),
743+
ReqBodyUsage: codespec.AllRequestBodies,
744+
},
745+
{
746+
TFSchemaName: "dynamic_value",
747+
TFModelName: "DynamicValue",
748+
ComputedOptionalRequired: codespec.Optional,
749+
String: &codespec.StringAttribute{},
750+
CustomType: &codespec.CustomTypeJSONVar,
751+
Description: conversion.StringPtr("Dynamic value."),
752+
ReqBodyUsage: codespec.AllRequestBodies,
753+
},
754+
{
755+
TFSchemaName: "object_of_dynamic_values",
756+
TFModelName: "ObjectOfDynamicValues",
757+
ComputedOptionalRequired: codespec.Optional,
758+
CustomType: codespec.NewCustomMapType(codespec.CustomTypeJSON),
759+
Map: &codespec.MapAttribute{
760+
ElementType: codespec.CustomTypeJSON,
761+
},
762+
Description: conversion.StringPtr("Object of dynamic values."),
763+
ReqBodyUsage: codespec.AllRequestBodies,
764+
},
765+
},
766+
},
767+
Name: "test_dynamic_json_properties",
768+
PackageName: "testdynamicjsonproperties",
769+
Operations: codespec.APIOperations{
770+
Create: codespec.APIOperation{
771+
Path: "/api/atlas/v2/dynamicJsonProperties",
772+
HTTPMethod: "POST",
773+
},
774+
Read: codespec.APIOperation{
775+
Path: "/api/atlas/v2/dynamicJsonProperties",
776+
HTTPMethod: "GET",
777+
},
778+
Update: codespec.APIOperation{
779+
Path: "/api/atlas/v2/dynamicJsonProperties",
780+
HTTPMethod: "PATCH",
781+
},
782+
Delete: &codespec.APIOperation{
783+
Path: "/api/atlas/v2/dynamicJsonProperties",
784+
HTTPMethod: "DELETE",
785+
},
786+
VersionHeader: "application/vnd.atlas.2024-05-30+json",
787+
},
788+
}},
789+
},
790+
}
791+
runTestCase(t, tc)
792+
}
793+
723794
func runTestCase(t *testing.T, tc convertToSpecTestCase) {
724795
t.Helper()
725796
result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, &tc.inputResourceName)

0 commit comments

Comments
 (0)