From 16d93c251a3f2df96ddddacae04f6a23df74fd2a Mon Sep 17 00:00:00 2001 From: Alex Petraky <petraky@perx.ru> Date: Mon, 28 Apr 2025 20:07:40 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20MergeItemData=20=D0=B2=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=20It?= =?UTF-8?q?ems,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D0=B0=D1=8F=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D1=8F=D0=B5=D1=82=20=D1=81=D0=BB?= =?UTF-8?q?=D0=B8=D1=8F=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=B4=D0=B2=D1=83=D1=85=20=D0=BE=D0=B1=D1=8A=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D0=B2=20Item=20=D1=81=20=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=BC=20=D1=81=D1=85=D0=B5=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #3191 --- pkg/items/item.go | 29 +++++ pkg/items/item_test.go | 242 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) diff --git a/pkg/items/item.go b/pkg/items/item.go index dc56fe63..92ef2494 100644 --- a/pkg/items/item.go +++ b/pkg/items/item.go @@ -11,6 +11,7 @@ import ( "git.perx.ru/perxis/perxis-go/pkg/schema" "git.perx.ru/perxis/perxis-go/pkg/schema/field" "git.perx.ru/perxis/perxis-go/pkg/schema/localizer" + "git.perx.ru/perxis/perxis-go/pkg/schema/walk" pb "git.perx.ru/perxis/perxis-go/proto/items" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -662,3 +663,31 @@ func GetItemIDs(arr []*Item) []string { } return res } + +func MergeItemData(ctx context.Context, sch *schema.Schema, origData, updData map[string]any) (map[string]any, error) { + if origData == nil { + return updData, nil + } + + w := walk.NewWalker(sch, &walk.WalkConfig{}) + w.DefaultFn = func(c *walk.WalkContext) error { + if c.Src == nil || c.Dst != nil { + return nil + } + c.Dst = c.Src + c.Changed = true + return nil + } + + res, _, err := w.DataWalk(ctx, updData, origData) + if err != nil { + return nil, err + } + + v, ok := res.(map[string]any) + if !ok { + return nil, fmt.Errorf("expected map[string]interface{}, got %[1]T, %[1]v", res) + } + + return v, nil +} diff --git a/pkg/items/item_test.go b/pkg/items/item_test.go index 05baa60a..1344444e 100644 --- a/pkg/items/item_test.go +++ b/pkg/items/item_test.go @@ -264,3 +264,245 @@ func TestItem_Encode_Decode(t *testing.T) { }) } } + +func Test_mergeItemData(t *testing.T) { + tests := []struct { + name string + schema *schema.Schema + origData map[string]any + updData map[string]any + want map[string]interface{} + wantErr bool + }{ + { + name: "merge with non-nil original data", + schema: schema.New( + "field1", field.String(), + "field2", field.String(), + "field3", field.String(), + ), + origData: map[string]interface{}{ + "field1": "value1", + "field2": "value2", + }, + updData: map[string]interface{}{ + "field2": "new_value2", + "field3": "value3", + }, + want: map[string]interface{}{ + "field1": "value1", + "field2": "new_value2", + "field3": "value3", + }, + wantErr: false, + }, + { + name: "merge with nil original data", + schema: schema.New( + "field1", field.String(), + ), + origData: nil, + updData: map[string]interface{}{ + "field1": "value1", + }, + want: map[string]interface{}{ + "field1": "value1", + }, + wantErr: false, + }, + { + name: "merge with empty original data", + schema: schema.New( + "field1", field.String(), + ), + origData: map[string]interface{}{}, + updData: map[string]interface{}{ + "field1": "value1", + }, + want: map[string]interface{}{ + "field1": "value1", + }, + wantErr: false, + }, + { + name: "merge with schema fields", + schema: schema.New( + "field1", field.String(), + "field2", field.String(), + "field3", field.String(), + ), + origData: map[string]interface{}{ + "field1": "value1", + "field2": "value2", + }, + updData: map[string]interface{}{ + "field2": "new_value2", + "field3": "value3", + }, + want: map[string]interface{}{ + "field1": "value1", + "field2": "new_value2", + "field3": "value3", + }, + wantErr: false, + }, + { + name: "merge with extra fields not in schema", + schema: schema.New( + "field1", field.String(), + "field2", field.String(), + ), + origData: map[string]interface{}{ + "field1": "value1", + "extra_field": "extra_value", + }, + updData: map[string]interface{}{ + "field2": "value2", + "another_extra": "another_value", + }, + want: map[string]interface{}{ + "field1": "value1", + "field2": "value2", + }, + wantErr: false, + }, + { + name: "merge with different field types", + schema: schema.New( + "string_field", field.String(), + "number_field", field.Number(field.NumberFormatInt), + "bool_field", field.String(), + ), + origData: map[string]interface{}{ + "string_field": "old_value", + "number_field": 42, + }, + updData: map[string]interface{}{ + "string_field": "new_value", + "bool_field": "true", + }, + want: map[string]interface{}{ + "string_field": "new_value", + "number_field": 42, + "bool_field": "true", + }, + wantErr: false, + }, + { + name: "merge with nested schema", + schema: schema.New( + "user", field.Object( + "name", field.String(), + "age", field.Number(field.NumberFormatInt), + "active", field.Bool(), + ), + "metadata", field.Object( + "created_at", field.String(), + "updated_at", field.String(), + ), + ), + origData: map[string]interface{}{ + "user": map[string]interface{}{ + "name": "John", + "age": 30, + "active": true, + }, + "metadata": map[string]interface{}{ + "created_at": "2024-01-01", + }, + }, + updData: map[string]interface{}{ + "user": map[string]interface{}{ + "name": "John Doe", + }, + "metadata": map[string]interface{}{ + "updated_at": "2024-03-20", + }, + }, + want: map[string]interface{}{ + "user": map[string]interface{}{ + "name": "John Doe", + "age": 30, + "active": true, + }, + "metadata": map[string]interface{}{ + "created_at": "2024-01-01", + "updated_at": "2024-03-20", + }, + }, + wantErr: false, + }, + { + name: "merge with array fields", + schema: schema.New( + "tags", field.Array(field.String()), + "numbers", field.Array(field.Number(field.NumberFormatInt)), + "mixed", field.Array(field.String()), + ), + origData: map[string]interface{}{ + "tags": []interface{}{"tag1", "tag2"}, + "numbers": []interface{}{1, 2, 3}, + }, + updData: map[string]interface{}{ + "tags": []interface{}{"tag3", "tag4"}, + "mixed": []interface{}{"value1", "value2"}, + }, + want: map[string]interface{}{ + "tags": []interface{}{"tag3", "tag4"}, + "numbers": []interface{}{1, 2, 3}, + "mixed": []interface{}{"value1", "value2"}, + }, + wantErr: false, + }, + { + name: "merge with required fields", + schema: schema.New( + "required_field", field.String(), + "optional_field", field.String(), + ), + origData: map[string]interface{}{ + "required_field": "original", + "optional_field": "optional", + }, + updData: map[string]interface{}{ + "required_field": "updated", + }, + want: map[string]interface{}{ + "required_field": "updated", + "optional_field": "optional", + }, + wantErr: false, + }, + { + name: "merge with validation rules", + schema: schema.New( + "email", field.String(), + "age", field.Number(field.NumberFormatInt), + ), + origData: map[string]interface{}{ + "email": "test@example.com", + "age": 25, + }, + updData: map[string]interface{}{ + "email": "new@example.com", + "age": 30, + }, + want: map[string]interface{}{ + "email": "new@example.com", + "age": 30, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MergeItemData(context.Background(), tt.schema, tt.origData, tt.updData) + if (err != nil) != tt.wantErr { + t.Errorf("mergeItemData() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} -- GitLab