From 4c78c75ab28f68d7ca1c4aa77335390185992fc3 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:07:40 +0200 Subject: [PATCH 01/14] create Marshal --- internal/common/autogeneration/marshal.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index 1adfb3abd9..4662af19a8 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -10,6 +10,11 @@ import ( "github.com/huandu/xstrings" ) +// Marshal gets Terraform model and marshals it in JSON (e.g. for an Atlas request). +func Marshal(src any) ([]byte, error) { + return nil, nil +} + // Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model. // It supports the following Terraform model types: String, Bool, Int64, Float64. func Unmarshal(raw []byte, dest any) error { From f0527b2041f9f9d924c28156e755fac5f1a21f35 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:20:45 +0200 Subject: [PATCH 02/14] marshal string --- internal/common/autogeneration/marshal.go | 30 ++++++++++++++++--- .../common/autogeneration/marshal_test.go | 22 ++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index 4662af19a8..5ef72f9b04 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -10,9 +10,31 @@ import ( "github.com/huandu/xstrings" ) -// Marshal gets Terraform model and marshals it in JSON (e.g. for an Atlas request). +// Marshal gets Terraform model and marshals it into JSON (e.g. for an Atlas request). func Marshal(src any) ([]byte, error) { - return nil, nil + valSrc := reflect.ValueOf(src) + if valSrc.Kind() != reflect.Ptr { + panic("src must be pointer") + } + valSrc = valSrc.Elem() + if valSrc.Kind() != reflect.Struct { + panic("src must be pointer to struct") + } + dest := make(map[string]any) + for i := 0; i < valSrc.NumField(); i++ { + nameAttr := xstrings.ToSnakeCase(valSrc.Type().Field(i).Name) + valAttr := valSrc.Field(i) + switch v := valAttr.Interface().(type) { + case types.String: + if v.IsNull() || v.IsUnknown() { + continue + } + dest[nameAttr] = v.ValueString() + default: + return nil, fmt.Errorf("marshal not supported yet for type %T for field %s", v, nameAttr) + } + } + return json.Marshal(dest) } // Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model. @@ -61,14 +83,14 @@ func mapField(nameAttrSrc string, valueAttrSrc any, valDest reflect.Value) error case nil: return nil // skip nil values, no need to set anything default: - return fmt.Errorf("not supported yet type %T for field %s", v, nameAttrSrc) + return fmt.Errorf("unmarshal not supported yet for type %T for field %s", v, nameAttrSrc) } } func assignField(nameDest string, fieldDest reflect.Value, valueDest attr.Value) error { valObj := reflect.ValueOf(valueDest) if !fieldDest.Type().AssignableTo(valObj.Type()) { - return fmt.Errorf("can't assign value to model field %s", nameDest) + return fmt.Errorf("unmarshal can't assign value to model field %s", nameDest) } fieldDest.Set(valObj) return nil diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 138bc10406..7d7b9e757f 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -9,6 +9,24 @@ import ( "github.com/stretchr/testify/require" ) +func TestMarshalBasic(t *testing.T) { + model := struct { + AttributeString types.String `tfsdk:"attribute_string"` + }{ + AttributeString: types.StringValue("hello"), + } + const ( + expectedJSON = ` + { + "attribute_string": "hello" + } + ` + ) + rawJSON, err := autogeneration.Marshal(&model) + require.NoError(t, err) + assert.JSONEq(t, expectedJSON, string(rawJSON)) +} + func TestUnmarshalBasic(t *testing.T) { var model struct { AttributeFloat types.Float64 `tfsdk:"attribute_float"` @@ -132,11 +150,11 @@ func TestUnmarshalUnsupportedResponse(t *testing.T) { }{ "JSON objects not support yet": { responseJSON: `{"attr": {"key": "value"}}`, - errorStr: "not supported yet type map[string]interface {} for field attr", + errorStr: "unmarshal not supported yet for type map[string]interface {} for field attr", }, "JSON arrays not supported yet": { responseJSON: `{"attr": [{"key": "value"}]}`, - errorStr: "not supported yet type []interface {} for field attr", + errorStr: "unmarshal not supported yet for type []interface {} for field attr", }, } for name, tc := range testCases { From 39dde8bcce721a466834e43479ccbfb723a63eff Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:01:37 +0200 Subject: [PATCH 03/14] make marshal and umarshal func naming more consistent --- internal/common/autogeneration/marshal.go | 112 +++++++++++------- .../common/autogeneration/marshal_test.go | 5 +- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index 5ef72f9b04..a9e16f5eda 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -11,87 +11,107 @@ import ( ) // Marshal gets Terraform model and marshals it into JSON (e.g. for an Atlas request). -func Marshal(src any) ([]byte, error) { - valSrc := reflect.ValueOf(src) - if valSrc.Kind() != reflect.Ptr { - panic("src must be pointer") +func Marshal(model any) ([]byte, error) { + valModel := reflect.ValueOf(model) + if valModel.Kind() != reflect.Ptr { + panic("Marshal expects a pointer") } - valSrc = valSrc.Elem() - if valSrc.Kind() != reflect.Struct { - panic("src must be pointer to struct") + valModel = valModel.Elem() + if valModel.Kind() != reflect.Struct { + panic("Marshal expects a pointer to struct") } - dest := make(map[string]any) - for i := 0; i < valSrc.NumField(); i++ { - nameAttr := xstrings.ToSnakeCase(valSrc.Type().Field(i).Name) - valAttr := valSrc.Field(i) - switch v := valAttr.Interface().(type) { - case types.String: - if v.IsNull() || v.IsUnknown() { - continue - } - dest[nameAttr] = v.ValueString() - default: - return nil, fmt.Errorf("marshal not supported yet for type %T for field %s", v, nameAttr) + objJSON, err := marshalAttrs(valModel) + if err != nil { + return nil, err + } + return json.Marshal(objJSON) +} + +func marshalAttrs(valModel reflect.Value) (map[string]any, error) { + objJSON := make(map[string]any) + for i := 0; i < valModel.NumField(); i++ { + attrNameModel := valModel.Type().Field(i).Name + attrValModel := valModel.Field(i) + if err := marshalAttr(attrNameModel, attrValModel, objJSON); err != nil { + return nil, err + } + } + return objJSON, nil +} + +func marshalAttr(attrNameModel string, attrValModel reflect.Value, objJSON map[string]any) error { + attrNameJSON := xstrings.ToSnakeCase(attrNameModel) + if v, ok := attrValModel.Interface().(attr.Value); ok { + if v.IsNull() || v.IsUnknown() { + return nil // skip nil or unknown values } } - return json.Marshal(dest) + switch v := attrValModel.Interface().(type) { + case types.String: + objJSON[attrNameJSON] = v.ValueString() + case types.Int64: + objJSON[attrNameJSON] = v.ValueInt64() + default: + return fmt.Errorf("marshal not supported yet for type %T for field %s", v, attrNameJSON) + } + return nil } // Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model. // It supports the following Terraform model types: String, Bool, Int64, Float64. -func Unmarshal(raw []byte, dest any) error { - var src map[string]any - if err := json.Unmarshal(raw, &src); err != nil { +func Unmarshal(raw []byte, model any) error { + var objJSON map[string]any + if err := json.Unmarshal(raw, &objJSON); err != nil { return err } - return mapFields(src, dest) + return unmarshalAttrs(objJSON, model) } -func mapFields(src map[string]any, dest any) error { - valDest := reflect.ValueOf(dest) - if valDest.Kind() != reflect.Ptr { +func unmarshalAttrs(objJSON map[string]any, model any) error { + valModel := reflect.ValueOf(model) + if valModel.Kind() != reflect.Ptr { panic("dest must be pointer") } - valDest = valDest.Elem() - if valDest.Kind() != reflect.Struct { + valModel = valModel.Elem() + if valModel.Kind() != reflect.Struct { panic("dest must be pointer to struct") } - for nameAttrSrc, valueAttrSrc := range src { - if err := mapField(nameAttrSrc, valueAttrSrc, valDest); err != nil { + for attrNameJSON, attrObjJSON := range objJSON { + if err := unmarshalAttr(attrNameJSON, attrObjJSON, valModel); err != nil { return err } } return nil } -func mapField(nameAttrSrc string, valueAttrSrc any, valDest reflect.Value) error { - nameDest := xstrings.ToPascalCase(nameAttrSrc) - fieldDest := valDest.FieldByName(nameDest) - if !fieldDest.CanSet() { +func unmarshalAttr(attrNameJSON string, attrObjJSON any, valModel reflect.Value) error { + attrNameModel := xstrings.ToPascalCase(attrNameJSON) + fieldModel := valModel.FieldByName(attrNameModel) + if !fieldModel.CanSet() { return nil // skip fields that cannot be set, are invalid or not found } - switch v := valueAttrSrc.(type) { + switch v := attrObjJSON.(type) { case string: - return assignField(nameDest, fieldDest, types.StringValue(v)) + return setAttrModel(attrNameModel, fieldModel, types.StringValue(v)) case bool: - return assignField(nameDest, fieldDest, types.BoolValue(v)) + return setAttrModel(attrNameModel, fieldModel, types.BoolValue(v)) case float64: // number: try int or float - if assignField(nameDest, fieldDest, types.Float64Value(v)) == nil { + if setAttrModel(attrNameModel, fieldModel, types.Float64Value(v)) == nil { return nil } - return assignField(nameDest, fieldDest, types.Int64Value(int64(v))) + return setAttrModel(attrNameModel, fieldModel, types.Int64Value(int64(v))) case nil: return nil // skip nil values, no need to set anything default: - return fmt.Errorf("unmarshal not supported yet for type %T for field %s", v, nameAttrSrc) + return fmt.Errorf("unmarshal not supported yet for type %T for field %s", v, attrNameJSON) } } -func assignField(nameDest string, fieldDest reflect.Value, valueDest attr.Value) error { - valObj := reflect.ValueOf(valueDest) - if !fieldDest.Type().AssignableTo(valObj.Type()) { - return fmt.Errorf("unmarshal can't assign value to model field %s", nameDest) +func setAttrModel(name string, field reflect.Value, val attr.Value) error { + obj := reflect.ValueOf(val) + if !field.Type().AssignableTo(obj.Type()) { + return fmt.Errorf("unmarshal can't assign value to model field %s", name) } - fieldDest.Set(valObj) + field.Set(obj) return nil } diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 7d7b9e757f..87f85be9b0 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -12,13 +12,16 @@ import ( func TestMarshalBasic(t *testing.T) { model := struct { AttributeString types.String `tfsdk:"attribute_string"` + AttributeInt types.Int64 `tfsdk:"attribute_int"` }{ AttributeString: types.StringValue("hello"), + AttributeInt: types.Int64Value(1), } const ( expectedJSON = ` { - "attribute_string": "hello" + "attribute_string": "hello", + "attribute_int": 1 } ` ) From ac09871f2b57f69beb9e2ab8a9683ae1ec1f4a67 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:32:15 +0200 Subject: [PATCH 04/14] TestMarshalUnsupported and TestMarshalPanic --- internal/common/autogeneration/marshal.go | 32 ++++--- .../common/autogeneration/marshal_test.go | 91 ++++++++++++++++++- 2 files changed, 106 insertions(+), 17 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index a9e16f5eda..9cb8b70be8 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -27,6 +27,16 @@ func Marshal(model any) ([]byte, error) { return json.Marshal(objJSON) } +// Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model. +// It supports the following Terraform model types: String, Bool, Int64, Float64. +func Unmarshal(raw []byte, model any) error { + var objJSON map[string]any + if err := json.Unmarshal(raw, &objJSON); err != nil { + return err + } + return unmarshalAttrs(objJSON, model) +} + func marshalAttrs(valModel reflect.Value) (map[string]any, error) { objJSON := make(map[string]any) for i := 0; i < valModel.NumField(); i++ { @@ -41,32 +51,26 @@ func marshalAttrs(valModel reflect.Value) (map[string]any, error) { func marshalAttr(attrNameModel string, attrValModel reflect.Value, objJSON map[string]any) error { attrNameJSON := xstrings.ToSnakeCase(attrNameModel) - if v, ok := attrValModel.Interface().(attr.Value); ok { - if v.IsNull() || v.IsUnknown() { - return nil // skip nil or unknown values - } + obj, ok := attrValModel.Interface().(attr.Value) + if !ok { + panic("marshal expects only Terraform types") + } + if obj.IsNull() || obj.IsUnknown() { + return nil // skip nil or unknown values } switch v := attrValModel.Interface().(type) { case types.String: objJSON[attrNameJSON] = v.ValueString() case types.Int64: objJSON[attrNameJSON] = v.ValueInt64() + case types.Float64: + objJSON[attrNameJSON] = v.ValueFloat64() default: return fmt.Errorf("marshal not supported yet for type %T for field %s", v, attrNameJSON) } return nil } -// Unmarshal gets a JSON (e.g. from an Atlas response) and unmarshals it into a Terraform model. -// It supports the following Terraform model types: String, Bool, Int64, Float64. -func Unmarshal(raw []byte, model any) error { - var objJSON map[string]any - if err := json.Unmarshal(raw, &objJSON); err != nil { - return err - } - return unmarshalAttrs(objJSON, model) -} - func unmarshalAttrs(objJSON map[string]any, model any) error { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 87f85be9b0..6d24971257 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -3,6 +3,7 @@ package autogeneration_test import ( "testing" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogeneration" "github.com/stretchr/testify/assert" @@ -11,17 +12,24 @@ import ( func TestMarshalBasic(t *testing.T) { model := struct { - AttributeString types.String `tfsdk:"attribute_string"` - AttributeInt types.Int64 `tfsdk:"attribute_int"` + AttributeFloat types.Float64 `tfsdk:"attribute_float"` + AttributeString types.String `tfsdk:"attribute_string"` + AttributeUnkown types.String `tfsdk:"attribute_unknown"` + AttributeNull types.String `tfsdk:"attribute_null"` + AttributeInt types.Int64 `tfsdk:"attribute_int"` }{ + AttributeFloat: types.Float64Value(1.234), AttributeString: types.StringValue("hello"), + AttributeUnkown: types.StringUnknown(), // unknown values are not marshaled + AttributeNull: types.StringNull(), // null values are not marshaled AttributeInt: types.Int64Value(1), } const ( expectedJSON = ` { "attribute_string": "hello", - "attribute_int": 1 + "attribute_int": 1, + "attribute_float": 1.234 } ` ) @@ -30,6 +38,83 @@ func TestMarshalBasic(t *testing.T) { assert.JSONEq(t, expectedJSON, string(rawJSON)) } +func TestMarshalUnsupported(t *testing.T) { + testCases := map[string]any{ + "Object not supported yet, only no-nested types": &struct { + Attr types.Object + }{ + Attr: types.ObjectValueMust(map[string]attr.Type{ + "key": types.StringType, + }, map[string]attr.Value{ + "key": types.StringValue("value"), + }), + }, + "List not supported yet, only no-nested types": &struct { + Attr types.List + }{ + Attr: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("value"), + }), + }, + "Map not supported yet, only no-nested types": &struct { + Attr types.Map + }{ + Attr: types.MapValueMust(types.StringType, map[string]attr.Value{ + "key": types.StringValue("value"), + }), + }, + "Set not supported yet, only no-nested types": &struct { + Attr types.Set + }{ + Attr: types.SetValueMust(types.StringType, []attr.Value{ + types.StringValue("value"), + }), + }, + "Int32 not supported yet as it's not being used in any model": &struct { + Attr types.Int32 + }{ + Attr: types.Int32Value(1), + }, + "Float32 not supported yet as it's not being used in any model": &struct { + Attr types.Float32 + }{ + Attr: types.Float32Value(1.0), + }, + } + for name, model := range testCases { + t.Run(name, func(t *testing.T) { + raw, err := autogeneration.Marshal(model) + require.Error(t, err) + assert.Nil(t, raw) + }) + } +} + +func TestMarshalPanic(t *testing.T) { + str := "string" + testCases := map[string]any{ + "no Terraform types": &struct { + Attr string + }{ + Attr: "a", + }, + "no pointer": struct { + Attr types.String + }{ + Attr: types.StringValue("a"), + }, + "no struct": &str, + } + for name, model := range testCases { + t.Run(name, func(t *testing.T) { + assert.Panics(t, func() { + _, _ = autogeneration.Marshal(model) + }) + }) + } + +} + func TestUnmarshalBasic(t *testing.T) { var model struct { AttributeFloat types.Float64 `tfsdk:"attribute_float"` From 3f1764442ce26abf7fd2fe16577d79069d560828 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:34:49 +0200 Subject: [PATCH 05/14] panic messages --- internal/common/autogeneration/marshal.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index 9cb8b70be8..9a294007fa 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -14,11 +14,11 @@ import ( func Marshal(model any) ([]byte, error) { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { - panic("Marshal expects a pointer") + panic("model must be pointer") } valModel = valModel.Elem() if valModel.Kind() != reflect.Struct { - panic("Marshal expects a pointer to struct") + panic("model must be pointer to struct") } objJSON, err := marshalAttrs(valModel) if err != nil { @@ -53,7 +53,7 @@ func marshalAttr(attrNameModel string, attrValModel reflect.Value, objJSON map[s attrNameJSON := xstrings.ToSnakeCase(attrNameModel) obj, ok := attrValModel.Interface().(attr.Value) if !ok { - panic("marshal expects only Terraform types") + panic("marshal expects only Terraform types in the model") } if obj.IsNull() || obj.IsUnknown() { return nil // skip nil or unknown values @@ -74,11 +74,11 @@ func marshalAttr(attrNameModel string, attrValModel reflect.Value, objJSON map[s func unmarshalAttrs(objJSON map[string]any, model any) error { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { - panic("dest must be pointer") + panic("model must be pointer") } valModel = valModel.Elem() if valModel.Kind() != reflect.Struct { - panic("dest must be pointer to struct") + panic("model must be pointer to struct") } for attrNameJSON, attrObjJSON := range objJSON { if err := unmarshalAttr(attrNameJSON, attrObjJSON, valModel); err != nil { From efbde626a5574d234db10cba37d2e6459ffb776e Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:44:29 +0200 Subject: [PATCH 06/14] failing test for omitjson --- .../common/autogeneration/marshal_test.go | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 6d24971257..bad4131b7e 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -12,24 +12,26 @@ import ( func TestMarshalBasic(t *testing.T) { model := struct { - AttributeFloat types.Float64 `tfsdk:"attribute_float"` - AttributeString types.String `tfsdk:"attribute_string"` - AttributeUnkown types.String `tfsdk:"attribute_unknown"` - AttributeNull types.String `tfsdk:"attribute_null"` - AttributeInt types.Int64 `tfsdk:"attribute_int"` + AttrFloat types.Float64 `tfsdk:"attribute_float"` + AttrString types.String `tfsdk:"attribute_string"` + AttrOmit types.String `tfsdk:"attribute_omit" autogeneration:"omitjson"` + AttrUnkown types.String `tfsdk:"attribute_unknown"` + AttrNull types.String `tfsdk:"attribute_null"` + AttrInt types.Int64 `tfsdk:"attribute_int"` }{ - AttributeFloat: types.Float64Value(1.234), - AttributeString: types.StringValue("hello"), - AttributeUnkown: types.StringUnknown(), // unknown values are not marshaled - AttributeNull: types.StringNull(), // null values are not marshaled - AttributeInt: types.Int64Value(1), + AttrFloat: types.Float64Value(1.234), + AttrString: types.StringValue("hello"), + AttrOmit: types.StringValue("omit"), // values with tag `omitjson` are not marshaled + AttrUnkown: types.StringUnknown(), // unknown values are not marshaled + AttrNull: types.StringNull(), // null values are not marshaled + AttrInt: types.Int64Value(1), } const ( expectedJSON = ` { - "attribute_string": "hello", - "attribute_int": 1, - "attribute_float": 1.234 + "attr_string": "hello", + "attr_int": 1, + "attr_float": 1.234 } ` ) @@ -112,19 +114,18 @@ func TestMarshalPanic(t *testing.T) { }) }) } - } func TestUnmarshalBasic(t *testing.T) { var model struct { - AttributeFloat types.Float64 `tfsdk:"attribute_float"` - AttributeFloatWithInt types.Float64 `tfsdk:"attribute_float_with_int"` - AttributeString types.String `tfsdk:"attribute_string"` - AttributeNotInJSON types.String `tfsdk:"attribute_not_in_json"` - AttributeInt types.Int64 `tfsdk:"attribute_int"` - AttributeIntWithFloat types.Int64 `tfsdk:"attribute_int_with_float"` - AttributeTrue types.Bool `tfsdk:"attribute_true"` - AttributeFalse types.Bool `tfsdk:"attribute_false"` + AttrFloat types.Float64 `tfsdk:"attribute_float"` + AttrFloatWithInt types.Float64 `tfsdk:"attribute_float_with_int"` + AttrString types.String `tfsdk:"attribute_string"` + AttrNotInJSON types.String `tfsdk:"attribute_not_in_json"` + AttrInt types.Int64 `tfsdk:"attribute_int"` + AttrIntWithFloat types.Int64 `tfsdk:"attribute_int_with_float"` + AttrTrue types.Bool `tfsdk:"attribute_true"` + AttrFalse types.Bool `tfsdk:"attribute_false"` } const ( epsilon = 10e-15 // float tolerance @@ -132,27 +133,27 @@ func TestUnmarshalBasic(t *testing.T) { // attribute_null is ignored because it is null, no error is thrown even if it is not in the model. tfResponseJSON = ` { - "attribute_string": "value_string", - "attribute_true": true, - "attribute_false": false, - "attribute_int": 123, - "attribute_int_with_float": 10.6, - "attribute_float": 456.1, - "attribute_float_with_int": 13, - "attribute_not_in_model": "val", - "attribute_null": null + "attr_string": "value_string", + "attr_true": true, + "attr_false": false, + "attr_int": 123, + "attr_int_with_float": 10.6, + "attr_float": 456.1, + "attr_float_with_int": 13, + "attr_not_in_model": "val", + "attr_null": null } ` ) require.NoError(t, autogeneration.Unmarshal([]byte(tfResponseJSON), &model)) - assert.Equal(t, "value_string", model.AttributeString.ValueString()) - assert.True(t, model.AttributeTrue.ValueBool()) - assert.False(t, model.AttributeFalse.ValueBool()) - assert.Equal(t, int64(123), model.AttributeInt.ValueInt64()) - assert.Equal(t, int64(10), model.AttributeIntWithFloat.ValueInt64()) // response floats stored in model ints have their decimals stripped. - assert.InEpsilon(t, float64(456.1), model.AttributeFloat.ValueFloat64(), epsilon) - assert.InEpsilon(t, float64(13), model.AttributeFloatWithInt.ValueFloat64(), epsilon) - assert.True(t, model.AttributeNotInJSON.IsNull()) // attributes not in JSON response are not changed, so null is kept. + assert.Equal(t, "value_string", model.AttrString.ValueString()) + assert.True(t, model.AttrTrue.ValueBool()) + assert.False(t, model.AttrFalse.ValueBool()) + assert.Equal(t, int64(123), model.AttrInt.ValueInt64()) + assert.Equal(t, int64(10), model.AttrIntWithFloat.ValueInt64()) // response floats stored in model ints have their decimals stripped. + assert.InEpsilon(t, float64(456.1), model.AttrFloat.ValueFloat64(), epsilon) + assert.InEpsilon(t, float64(13), model.AttrFloatWithInt.ValueFloat64(), epsilon) + assert.True(t, model.AttrNotInJSON.IsNull()) // attributes not in JSON response are not changed, so null is kept. } func TestUnmarshalErrors(t *testing.T) { From 5533fe98dfd20352ad2f7cac9658eae2db2b9a89 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:47:22 +0200 Subject: [PATCH 07/14] typo --- internal/common/autogeneration/marshal.go | 2 +- internal/common/autogeneration/marshal_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index 9a294007fa..d932d4ef82 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -10,7 +10,7 @@ import ( "github.com/huandu/xstrings" ) -// Marshal gets Terraform model and marshals it into JSON (e.g. for an Atlas request). +// Marshal gets a Terraform model and marshals it into JSON (e.g. for an Atlas request). func Marshal(model any) ([]byte, error) { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index bad4131b7e..ba74823386 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -35,9 +35,9 @@ func TestMarshalBasic(t *testing.T) { } ` ) - rawJSON, err := autogeneration.Marshal(&model) + raw, err := autogeneration.Marshal(&model) require.NoError(t, err) - assert.JSONEq(t, expectedJSON, string(rawJSON)) + assert.JSONEq(t, expectedJSON, string(raw)) } func TestMarshalUnsupported(t *testing.T) { From f773342dfca54af6af1365c057b7b0bca7f1a46b Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 20:57:05 +0200 Subject: [PATCH 08/14] implement omitjson --- internal/common/autogeneration/marshal.go | 12 +++++++++- .../common/autogeneration/marshal_test.go | 23 +++++++++++-------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index d932d4ef82..d7b6489d25 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -4,12 +4,18 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/huandu/xstrings" ) +const ( + tagKeyAutogeneration = "autogeneration" + tagValueOmitJSON = "omitjson" +) + // Marshal gets a Terraform model and marshals it into JSON (e.g. for an Atlas request). func Marshal(model any) ([]byte, error) { valModel := reflect.ValueOf(model) @@ -40,7 +46,11 @@ func Unmarshal(raw []byte, model any) error { func marshalAttrs(valModel reflect.Value) (map[string]any, error) { objJSON := make(map[string]any) for i := 0; i < valModel.NumField(); i++ { - attrNameModel := valModel.Type().Field(i).Name + attrTypeModel := valModel.Type().Field(i) + if strings.Contains(attrTypeModel.Tag.Get(tagKeyAutogeneration), tagValueOmitJSON) { + continue // skip fields with tag `omitjson` + } + attrNameModel := attrTypeModel.Name attrValModel := valModel.Field(i) if err := marshalAttr(attrNameModel, attrValModel, objJSON); err != nil { return nil, err diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index ba74823386..3a6c5c6c5f 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -14,17 +14,20 @@ func TestMarshalBasic(t *testing.T) { model := struct { AttrFloat types.Float64 `tfsdk:"attribute_float"` AttrString types.String `tfsdk:"attribute_string"` - AttrOmit types.String `tfsdk:"attribute_omit" autogeneration:"omitjson"` - AttrUnkown types.String `tfsdk:"attribute_unknown"` - AttrNull types.String `tfsdk:"attribute_null"` - AttrInt types.Int64 `tfsdk:"attribute_int"` + // values with tag `omitjson` are not marshaled, and they don't need to be Terraform types + AttrOmit types.String `tfsdk:"attribute_omit" autogeneration:"omitjson"` + AttrOmitNoTerraform string `autogeneration:"omitjson"` + AttrUnkown types.String `tfsdk:"attribute_unknown"` + AttrNull types.String `tfsdk:"attribute_null"` + AttrInt types.Int64 `tfsdk:"attribute_int"` }{ - AttrFloat: types.Float64Value(1.234), - AttrString: types.StringValue("hello"), - AttrOmit: types.StringValue("omit"), // values with tag `omitjson` are not marshaled - AttrUnkown: types.StringUnknown(), // unknown values are not marshaled - AttrNull: types.StringNull(), // null values are not marshaled - AttrInt: types.Int64Value(1), + AttrFloat: types.Float64Value(1.234), + AttrString: types.StringValue("hello"), + AttrOmit: types.StringValue("omit"), + AttrOmitNoTerraform: "omit", + AttrUnkown: types.StringUnknown(), // unknown values are not marshaled + AttrNull: types.StringNull(), // null values are not marshaled + AttrInt: types.Int64Value(1), } const ( expectedJSON = ` From b590a98b18e54f4d2e153ae6c2b2625b08a99856 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:12:20 +0200 Subject: [PATCH 09/14] createonly --- internal/common/autogeneration/marshal.go | 17 +++++++---- .../common/autogeneration/marshal_test.go | 29 +++++++++++++++++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index d7b6489d25..c60cb67f7d 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -12,12 +12,13 @@ import ( ) const ( - tagKeyAutogeneration = "autogeneration" - tagValueOmitJSON = "omitjson" + tagKey = "autogeneration" + tagValOmitJSON = "omitjson" + tagValCreateOnly = "createonly" ) // Marshal gets a Terraform model and marshals it into JSON (e.g. for an Atlas request). -func Marshal(model any) ([]byte, error) { +func Marshal(model any, isCreate bool) ([]byte, error) { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { panic("model must be pointer") @@ -26,7 +27,7 @@ func Marshal(model any) ([]byte, error) { if valModel.Kind() != reflect.Struct { panic("model must be pointer to struct") } - objJSON, err := marshalAttrs(valModel) + objJSON, err := marshalAttrs(valModel, isCreate) if err != nil { return nil, err } @@ -43,13 +44,17 @@ func Unmarshal(raw []byte, model any) error { return unmarshalAttrs(objJSON, model) } -func marshalAttrs(valModel reflect.Value) (map[string]any, error) { +func marshalAttrs(valModel reflect.Value, isCreate bool) (map[string]any, error) { objJSON := make(map[string]any) for i := 0; i < valModel.NumField(); i++ { attrTypeModel := valModel.Type().Field(i) - if strings.Contains(attrTypeModel.Tag.Get(tagKeyAutogeneration), tagValueOmitJSON) { + tag := attrTypeModel.Tag.Get(tagKey) + if strings.Contains(tag, tagValOmitJSON) { continue // skip fields with tag `omitjson` } + if !isCreate && strings.Contains(tag, tagValCreateOnly) { + continue // skip fields with tag `createonly` if not createOnly + } attrNameModel := attrTypeModel.Name attrValModel := valModel.Field(i) if err := marshalAttr(attrNameModel, attrValModel, objJSON); err != nil { diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 3a6c5c6c5f..171d50e0f1 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -38,11 +38,34 @@ func TestMarshalBasic(t *testing.T) { } ` ) - raw, err := autogeneration.Marshal(&model) + raw, err := autogeneration.Marshal(&model, false) require.NoError(t, err) assert.JSONEq(t, expectedJSON, string(raw)) } +func TestMarshalCreateOnly(t *testing.T) { + const ( + expectedCreate = `{ "attr": "val1", "attr_create_only": "val2" }` + expectedNoCreate = `{ "attr": "val1" }` + ) + model := struct { + Attr types.String `tfsdk:"attr"` + AttrCreateOnly types.String `tfsdk:"attr_create_only" autogeneration:"createonly"` + AttrOmit types.String `tfsdk:"attr_omit" autogeneration:"omitjson"` + }{ + Attr: types.StringValue("val1"), + AttrCreateOnly: types.StringValue("val2"), + AttrOmit: types.StringValue("omit"), + } + noCreate, errNoCreate := autogeneration.Marshal(&model, false) + require.NoError(t, errNoCreate) + assert.JSONEq(t, expectedNoCreate, string(noCreate)) + + create, errCreate := autogeneration.Marshal(&model, true) + require.NoError(t, errCreate) + assert.JSONEq(t, expectedCreate, string(create)) +} + func TestMarshalUnsupported(t *testing.T) { testCases := map[string]any{ "Object not supported yet, only no-nested types": &struct { @@ -88,7 +111,7 @@ func TestMarshalUnsupported(t *testing.T) { } for name, model := range testCases { t.Run(name, func(t *testing.T) { - raw, err := autogeneration.Marshal(model) + raw, err := autogeneration.Marshal(model, false) require.Error(t, err) assert.Nil(t, raw) }) @@ -113,7 +136,7 @@ func TestMarshalPanic(t *testing.T) { for name, model := range testCases { t.Run(name, func(t *testing.T) { assert.Panics(t, func() { - _, _ = autogeneration.Marshal(model) + _, _ = autogeneration.Marshal(model, false) }) }) } From 5dcab181218ec08a4458695a331b68ff13cc6a9e Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Wed, 2 Apr 2025 21:14:05 +0200 Subject: [PATCH 10/14] adjust comment --- internal/common/autogeneration/marshal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index c60cb67f7d..e8a0400204 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -53,7 +53,7 @@ func marshalAttrs(valModel reflect.Value, isCreate bool) (map[string]any, error) continue // skip fields with tag `omitjson` } if !isCreate && strings.Contains(tag, tagValCreateOnly) { - continue // skip fields with tag `createonly` if not createOnly + continue // skip fields with tag `createonly` if not in create } attrNameModel := attrTypeModel.Name attrValModel := valModel.Field(i) From 2a5577ff29d09ea81775952a2e48eda11e65585e Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 3 Apr 2025 08:31:28 +0200 Subject: [PATCH 11/14] simplify const --- internal/common/autogeneration/marshal_test.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 171d50e0f1..2513fe94f6 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -29,15 +29,7 @@ func TestMarshalBasic(t *testing.T) { AttrNull: types.StringNull(), // null values are not marshaled AttrInt: types.Int64Value(1), } - const ( - expectedJSON = ` - { - "attr_string": "hello", - "attr_int": 1, - "attr_float": 1.234 - } - ` - ) + const expectedJSON = `{ "attr_string": "hello", "attr_int": 1, "attr_float": 1.234 }` raw, err := autogeneration.Marshal(&model, false) require.NoError(t, err) assert.JSONEq(t, expectedJSON, string(raw)) From d6c212b4cb421ea24dbcec6ceb1db79616b4cbbf Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:04:04 +0200 Subject: [PATCH 12/14] rename creatonly tag to omitjsonupdate --- internal/common/autogeneration/marshal.go | 19 +++++++++--------- .../common/autogeneration/marshal_test.go | 20 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index e8a0400204..aec87ecf84 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "reflect" - "strings" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" @@ -12,13 +11,13 @@ import ( ) const ( - tagKey = "autogeneration" - tagValOmitJSON = "omitjson" - tagValCreateOnly = "createonly" + tagKey = "autogeneration" + tagValOmitJSON = "omitjson" + tagValOmitJSONUpdate = "omitjsonupdate" ) // Marshal gets a Terraform model and marshals it into JSON (e.g. for an Atlas request). -func Marshal(model any, isCreate bool) ([]byte, error) { +func Marshal(model any, isUpdate bool) ([]byte, error) { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { panic("model must be pointer") @@ -27,7 +26,7 @@ func Marshal(model any, isCreate bool) ([]byte, error) { if valModel.Kind() != reflect.Struct { panic("model must be pointer to struct") } - objJSON, err := marshalAttrs(valModel, isCreate) + objJSON, err := marshalAttrs(valModel, isUpdate) if err != nil { return nil, err } @@ -44,16 +43,16 @@ func Unmarshal(raw []byte, model any) error { return unmarshalAttrs(objJSON, model) } -func marshalAttrs(valModel reflect.Value, isCreate bool) (map[string]any, error) { +func marshalAttrs(valModel reflect.Value, isUpdate bool) (map[string]any, error) { objJSON := make(map[string]any) for i := 0; i < valModel.NumField(); i++ { attrTypeModel := valModel.Type().Field(i) tag := attrTypeModel.Tag.Get(tagKey) - if strings.Contains(tag, tagValOmitJSON) { + if tag == tagValOmitJSON { continue // skip fields with tag `omitjson` } - if !isCreate && strings.Contains(tag, tagValCreateOnly) { - continue // skip fields with tag `createonly` if not in create + if isUpdate && tag == tagValOmitJSONUpdate { + continue // skip fields with tag `omitjsonupdate` if in update mode } attrNameModel := attrTypeModel.Name attrValModel := valModel.Field(i) diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 2513fe94f6..32d155eb9e 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -35,27 +35,27 @@ func TestMarshalBasic(t *testing.T) { assert.JSONEq(t, expectedJSON, string(raw)) } -func TestMarshalCreateOnly(t *testing.T) { +func TestMarshalOmitJSONUpdate(t *testing.T) { const ( - expectedCreate = `{ "attr": "val1", "attr_create_only": "val2" }` - expectedNoCreate = `{ "attr": "val1" }` + expectedCreate = `{ "attr": "val1", "attr_omit_update": "val2" }` + expectedUpdate = `{ "attr": "val1" }` ) model := struct { Attr types.String `tfsdk:"attr"` - AttrCreateOnly types.String `tfsdk:"attr_create_only" autogeneration:"createonly"` + AttrOmitUpdate types.String `tfsdk:"attr_create_only" autogeneration:"omitjsonupdate"` AttrOmit types.String `tfsdk:"attr_omit" autogeneration:"omitjson"` }{ Attr: types.StringValue("val1"), - AttrCreateOnly: types.StringValue("val2"), + AttrOmitUpdate: types.StringValue("val2"), AttrOmit: types.StringValue("omit"), } - noCreate, errNoCreate := autogeneration.Marshal(&model, false) - require.NoError(t, errNoCreate) - assert.JSONEq(t, expectedNoCreate, string(noCreate)) - - create, errCreate := autogeneration.Marshal(&model, true) + create, errCreate := autogeneration.Marshal(&model, false) require.NoError(t, errCreate) assert.JSONEq(t, expectedCreate, string(create)) + + update, errUpdate := autogeneration.Marshal(&model, true) + require.NoError(t, errUpdate) + assert.JSONEq(t, expectedUpdate, string(update)) } func TestMarshalUnsupported(t *testing.T) { From 065089b6fbe9b9dc8498c0ea0e0fcbb739a8ae8d Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:11:03 +0200 Subject: [PATCH 13/14] document Marshal --- internal/common/autogeneration/marshal.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index aec87ecf84..9093b5ab0b 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -17,6 +17,10 @@ const ( ) // Marshal gets a Terraform model and marshals it into JSON (e.g. for an Atlas request). +// It supports the following Terraform model types: String, Bool, Int64, Float64. +// Attributes that are null or unknown are not marshaled. +// Attributes with autogeneration tag `omitjson` are never marshaled. +// Attributes with autogeneration tag `omitjsonupdate` are not marshaled if isUpdate is true. func Marshal(model any, isUpdate bool) ([]byte, error) { valModel := reflect.ValueOf(model) if valModel.Kind() != reflect.Ptr { From bf5e83bc75103504a430ccb632f73530d033f2b7 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 3 Apr 2025 13:20:57 +0200 Subject: [PATCH 14/14] fix tfsdk tags --- internal/common/autogeneration/marshal.go | 2 +- .../common/autogeneration/marshal_test.go | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/common/autogeneration/marshal.go b/internal/common/autogeneration/marshal.go index 9093b5ab0b..46ba4c4802 100644 --- a/internal/common/autogeneration/marshal.go +++ b/internal/common/autogeneration/marshal.go @@ -74,7 +74,7 @@ func marshalAttr(attrNameModel string, attrValModel reflect.Value, objJSON map[s panic("marshal expects only Terraform types in the model") } if obj.IsNull() || obj.IsUnknown() { - return nil // skip nil or unknown values + return nil // skip null or unknown values } switch v := attrValModel.Interface().(type) { case types.String: diff --git a/internal/common/autogeneration/marshal_test.go b/internal/common/autogeneration/marshal_test.go index 32d155eb9e..572c6e2353 100644 --- a/internal/common/autogeneration/marshal_test.go +++ b/internal/common/autogeneration/marshal_test.go @@ -12,14 +12,14 @@ import ( func TestMarshalBasic(t *testing.T) { model := struct { - AttrFloat types.Float64 `tfsdk:"attribute_float"` - AttrString types.String `tfsdk:"attribute_string"` + AttrFloat types.Float64 `tfsdk:"attr_float"` + AttrString types.String `tfsdk:"attr_string"` // values with tag `omitjson` are not marshaled, and they don't need to be Terraform types - AttrOmit types.String `tfsdk:"attribute_omit" autogeneration:"omitjson"` + AttrOmit types.String `tfsdk:"attr_omit" autogeneration:"omitjson"` AttrOmitNoTerraform string `autogeneration:"omitjson"` - AttrUnkown types.String `tfsdk:"attribute_unknown"` - AttrNull types.String `tfsdk:"attribute_null"` - AttrInt types.Int64 `tfsdk:"attribute_int"` + AttrUnkown types.String `tfsdk:"attr_unknown"` + AttrNull types.String `tfsdk:"attr_null"` + AttrInt types.Int64 `tfsdk:"attr_int"` }{ AttrFloat: types.Float64Value(1.234), AttrString: types.StringValue("hello"), @@ -42,7 +42,7 @@ func TestMarshalOmitJSONUpdate(t *testing.T) { ) model := struct { Attr types.String `tfsdk:"attr"` - AttrOmitUpdate types.String `tfsdk:"attr_create_only" autogeneration:"omitjsonupdate"` + AttrOmitUpdate types.String `tfsdk:"attr_omit_update" autogeneration:"omitjsonupdate"` AttrOmit types.String `tfsdk:"attr_omit" autogeneration:"omitjson"` }{ Attr: types.StringValue("val1"), @@ -136,14 +136,14 @@ func TestMarshalPanic(t *testing.T) { func TestUnmarshalBasic(t *testing.T) { var model struct { - AttrFloat types.Float64 `tfsdk:"attribute_float"` - AttrFloatWithInt types.Float64 `tfsdk:"attribute_float_with_int"` - AttrString types.String `tfsdk:"attribute_string"` - AttrNotInJSON types.String `tfsdk:"attribute_not_in_json"` - AttrInt types.Int64 `tfsdk:"attribute_int"` - AttrIntWithFloat types.Int64 `tfsdk:"attribute_int_with_float"` - AttrTrue types.Bool `tfsdk:"attribute_true"` - AttrFalse types.Bool `tfsdk:"attribute_false"` + AttrFloat types.Float64 `tfsdk:"attr_float"` + AttrFloatWithInt types.Float64 `tfsdk:"attr_float_with_int"` + AttrString types.String `tfsdk:"attr_string"` + AttrNotInJSON types.String `tfsdk:"attr_not_in_json"` + AttrInt types.Int64 `tfsdk:"attr_int"` + AttrIntWithFloat types.Int64 `tfsdk:"attr_int_with_float"` + AttrTrue types.Bool `tfsdk:"attr_true"` + AttrFalse types.Bool `tfsdk:"attr_false"` } const ( epsilon = 10e-15 // float tolerance