Skip to content

Commit f91eba6

Browse files
authored
chore: Fix case formatting of generated TF model fields not matching API fields (#3740)
1 parent ea8dfda commit f91eba6

24 files changed

+361
-152
lines changed

internal/common/autogen/marshal.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +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/autogen/stringcase"
1112
)
1213

1314
const (
@@ -62,7 +63,7 @@ func marshalAttrs(valModel reflect.Value, isUpdate bool) (map[string]any, error)
6263
}
6364

6465
func marshalAttr(attrNameModel string, attrValModel reflect.Value, objJSON map[string]any, isUpdate, includeNullOnUpdate bool) error {
65-
attrNameJSON := toJSONName(attrNameModel)
66+
attrNameJSON := stringcase.Uncapitalize(attrNameModel)
6667
obj, ok := attrValModel.Interface().(attr.Value)
6768
if !ok {
6869
panic("marshal expects only Terraform types in the model")
@@ -141,9 +142,9 @@ func getMapAttr(elms map[string]attr.Value, keepKeyCase bool) (any, error) {
141142
return nil, err
142143
}
143144
if valChild != nil {
144-
nameJSON := toJSONName(name)
145-
if keepKeyCase {
146-
nameJSON = name
145+
nameJSON := name
146+
if !keepKeyCase {
147+
nameJSON = stringcase.ToCamelCase(name)
147148
}
148149
objJSON[nameJSON] = valChild
149150
}

internal/common/autogen/name.go

Lines changed: 0 additions & 17 deletions
This file was deleted.

tools/codegen/stringcase/string_case.go renamed to internal/common/autogen/stringcase/string_case.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"regexp"
66
"strings"
7+
"unicode"
8+
"unicode/utf8"
79

810
"github.com/huandu/xstrings"
911
)
@@ -46,3 +48,39 @@ func FromCamelCase(input string) SnakeCaseString {
4648

4749
return SnakeCaseString(strings.ToLower(insertedUnderscores))
4850
}
51+
52+
func ToCamelCase(str string) string {
53+
return xstrings.ToCamelCase(str)
54+
}
55+
56+
func ToSnakeCase(str string) string {
57+
return xstrings.ToSnakeCase(str)
58+
}
59+
60+
func Capitalize(str string) string {
61+
return capitalization(str, true)
62+
}
63+
64+
func Uncapitalize(str string) string {
65+
return capitalization(str, false)
66+
}
67+
68+
func capitalization(str string, capitalize bool) string {
69+
if str == "" {
70+
return str
71+
}
72+
73+
first, size := utf8.DecodeRuneInString(str)
74+
if first == utf8.RuneError {
75+
return str
76+
}
77+
78+
builder := &strings.Builder{}
79+
if capitalize {
80+
builder.WriteRune(unicode.ToUpper(first))
81+
} else {
82+
builder.WriteRune(unicode.ToLower(first))
83+
}
84+
builder.WriteString(str[size:])
85+
return builder.String()
86+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package stringcase_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogen/stringcase"
7+
)
8+
9+
func TestCapitalize(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
expected string
14+
}{
15+
{
16+
name: "Non capitalized to capitalized",
17+
input: "toCaps",
18+
expected: "ToCaps",
19+
},
20+
{
21+
name: "Already capitalized does nothing",
22+
input: "ToCaps",
23+
expected: "ToCaps",
24+
},
25+
{
26+
name: "Single char capitalizes",
27+
input: "c",
28+
expected: "C",
29+
},
30+
{
31+
name: "Non-capitalizable does nothing",
32+
input: "_",
33+
expected: "_",
34+
},
35+
{
36+
name: "Empty does nothing",
37+
input: "",
38+
expected: "",
39+
},
40+
{
41+
name: "Invalid utf8 does nothing",
42+
input: string([]byte{0xFF}),
43+
expected: string([]byte{0xFF}),
44+
},
45+
}
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
if actual := stringcase.Capitalize(tt.input); actual != tt.expected {
49+
t.Errorf("Capitalize() returned %v, expected %v", actual, tt.expected)
50+
}
51+
})
52+
}
53+
}
54+
55+
func TestUncapitalize(t *testing.T) {
56+
tests := []struct {
57+
name string
58+
input string
59+
expected string
60+
}{
61+
{
62+
name: "Capitalized to non capitalized",
63+
input: "FromCaps",
64+
expected: "fromCaps",
65+
},
66+
{
67+
name: "Non capitalized does nothing",
68+
input: "fromCaps",
69+
expected: "fromCaps",
70+
},
71+
{
72+
name: "Single char uncapitalizes",
73+
input: "C",
74+
expected: "c",
75+
},
76+
{
77+
name: "Non-uncapitalizable does nothing",
78+
input: "_",
79+
expected: "_",
80+
},
81+
{
82+
name: "Empty does nothing",
83+
input: "",
84+
expected: "",
85+
},
86+
{
87+
name: "Invalid utf8 does nothing",
88+
input: string([]byte{0xFF}),
89+
expected: string([]byte{0xFF}),
90+
},
91+
}
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
if actual := stringcase.Uncapitalize(tt.input); actual != tt.expected {
95+
t.Errorf("Uncapitalize() returned %v, expected %v", actual, tt.expected)
96+
}
97+
})
98+
}
99+
}

internal/common/autogen/unmarshal.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ 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/stringcase"
1314
)
1415

1516
// Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model.
@@ -45,7 +46,7 @@ func unmarshalAttrs(objJSON map[string]any, model any) error {
4546
}
4647

4748
func unmarshalAttr(attrNameJSON string, attrObjJSON any, valModel reflect.Value) error {
48-
attrNameModel := toTfModelName(attrNameJSON)
49+
attrNameModel := stringcase.Capitalize(attrNameJSON)
4950
fieldModel := valModel.FieldByName(attrNameModel)
5051
if !fieldModel.CanSet() {
5152
return nil // skip fields that cannot be set, are invalid or not found
@@ -75,7 +76,7 @@ func setAttrTfModel(name string, field reflect.Value, val attr.Value) error {
7576
}
7677

7778
func setObjElmAttrModel(name string, value any, mapAttrs map[string]attr.Value, mapTypes map[string]attr.Type) error {
78-
nameChildTf := toTfSchemaName(name)
79+
nameChildTf := stringcase.ToSnakeCase(name)
7980
valueType, found := mapTypes[nameChildTf]
8081
if !found {
8182
return nil // skip attributes that are not in the model
@@ -91,7 +92,7 @@ func setObjElmAttrModel(name string, value any, mapAttrs map[string]attr.Value,
9192
}
9293

9394
func getTfAttr(value any, valueType attr.Type, oldVal attr.Value, name string) (attr.Value, error) {
94-
nameErr := toTfSchemaName(name)
95+
nameErr := stringcase.ToSnakeCase(name)
9596
switch v := value.(type) {
9697
case string:
9798
if valueType == types.StringType {
@@ -164,7 +165,7 @@ func getTfAttr(value any, valueType attr.Type, oldVal attr.Value, name string) (
164165
}
165166

166167
func errUnmarshal(value any, valueType attr.Type, typeReceived, name string) error {
167-
nameErr := toTfSchemaName(name)
168+
nameErr := stringcase.ToSnakeCase(name)
168169
parts := strings.Split(reflect.TypeOf(valueType).String(), ".")
169170
typeErr := parts[len(parts)-1]
170171
return fmt.Errorf("unmarshal of attribute %s expects type %s but got %s with value: %v", nameErr, typeErr, typeReceived, value)

internal/serviceapi/clusterapi/resource_schema.go

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

internal/serviceapi/clusterapi/resource_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestAccClusterAPI_basic(t *testing.T) {
4343
ImportStateVerifyIdentifierAttribute: "name",
4444
ImportStateVerifyIgnore: []string{
4545
"retain_backups_enabled", // This field is TF specific and not returned by Atlas, so Import can't fill it in.
46-
"mongo_db_major_version", // Risks plan change of 8 --> 8.0 (always normalized to `major.minor`)
46+
"mongo_dbmajor_version", // Risks plan change of 8 --> 8.0 (always normalized to `major.minor`)
4747
"state_name", // Cluster state can change from IDLE to UPDATING and risks making the test flaky
4848
"delete_on_create_timeout", // This field is TF specific and not returned by Atlas, so Import can't fill it in.
4949
},

internal/serviceapi/databaseuserapi/resource_schema.go

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

0 commit comments

Comments
 (0)