Skip to content
Snippets Groups Projects
Commit 47f15972 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

Merge branch 'feature/3191-MergeItemData' into 'master'

[feature] Добавлена функция MergeItemData в пакет Items, которая выполняет слияние данных двух объектов Item с учетом схемы

See merge request perxis/perxis-go!456
parents ee199806 16d93c25
No related branches found
No related tags found
No related merge requests found
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"git.perx.ru/perxis/perxis-go/pkg/schema" "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/field"
"git.perx.ru/perxis/perxis-go/pkg/schema/localizer" "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" pb "git.perx.ru/perxis/perxis-go/proto/items"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
...@@ -662,3 +663,31 @@ func GetItemIDs(arr []*Item) []string { ...@@ -662,3 +663,31 @@ func GetItemIDs(arr []*Item) []string {
} }
return res 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
}
...@@ -264,3 +264,245 @@ func TestItem_Encode_Decode(t *testing.T) { ...@@ -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)
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment