diff --git a/pkg/collections/collection.go b/pkg/collections/collection.go index ed9f91afe038dc1ebcb4f42a91f51a7828b47cf9..3e574d4071327460ea5b49c5d5b66641655ddc6a 100644 --- a/pkg/collections/collection.go +++ b/pkg/collections/collection.go @@ -101,16 +101,27 @@ type Collection struct { } // GetID возвращает идентификатор коллекции -func (c Collection) GetID() string { +func (c *Collection) GetID() string { return c.ID } -func (c Collection) GetSpaceID() string { +func (c *Collection) GetSpaceID() string { return c.SpaceID } // Equal сравнивает две коллекции, за исключением Schema, Access, StateInfo и Config -func (c Collection) Equal(other *Collection) bool { +// Deprecated: используйте метод IsEqual +func (c *Collection) Equal(other *Collection) bool { + return c.IsEqual(other) +} + +func (c *Collection) IsEqual(other *Collection) bool { + if c == other { + return true + } + if c == nil || other == nil { + return false + } if c.ID != other.ID || c.SpaceID != other.SpaceID || c.EnvID != other.EnvID || @@ -132,6 +143,10 @@ func (c Collection) Equal(other *Collection) bool { return true } +func IsEqual(c1, c2 *Collection) bool { + return c1.IsEqual(c2) +} + type View struct { SpaceID string `json:"space_id" bson:"space_id"` // SpaceID оригинальной коллекции EnvID string `json:"environment_id" bson:"environment_id"` // EnvID оригинальной коллекции @@ -185,7 +200,7 @@ const ( StateChanged ) -func (c Collection) Clone() *Collection { +func (c *Collection) Clone() *Collection { clone := &Collection{ ID: c.ID, SpaceID: c.SpaceID, @@ -294,3 +309,116 @@ func RuleFromAccess(access *Access) *permission.Rule { Hidden: access.Hidden, } } + +// Merge объединяет две коллекции, возвращая новую коллекцию +// с объединенными данными. Если данные второй коллекции не пустые, +// то данные из второй коллекции перезаписывают данные из первой. +func (c *Collection) Merge(other *Collection) *Collection { + if c == nil { + if other == nil { + return nil + } + return other.Clone() + } + + if other == nil { + return c.Clone() + } + + merged := c.Clone() + merged.mergeScalarFields(other) + merged.mergePointerFields(other) + merged.mergeComplexFields(other) + + return merged +} + +// mergeScalarFields копирует скалярные поля из other коллекции в текущую коллекцию. +func (c *Collection) mergeScalarFields(other *Collection) { + if other.ID != "" { + c.ID = other.ID + } + if other.Name != "" { + c.Name = other.Name + } + if other.SpaceID != "" { + c.SpaceID = other.SpaceID + } + if other.EnvID != "" { + c.EnvID = other.EnvID + } + if other.Hidden { + c.Hidden = other.Hidden + } + if other.NoPublish { + c.NoPublish = other.NoPublish + } + if other.NoArchive { + c.NoArchive = other.NoArchive + } + if other.NoRevisions { + c.NoRevisions = other.NoRevisions + } + if other.MaxRevisions > 0 { + c.MaxRevisions = other.MaxRevisions + } + if other.RevisionTTL > 0 { + c.RevisionTTL = other.RevisionTTL + } +} + +// mergePointerFields копирует указатели из other коллекции в текущую коллекцию. +func (c *Collection) mergePointerFields(other *Collection) { + if other.NoData != nil { + c.NoData = other.NoData + } + if other.Single != nil { + single := *other.Single + c.Single = &single + } + if other.System != nil { + system := *other.System + c.System = &system + } + if other.StateInfo != nil { + info := *other.StateInfo + c.StateInfo = &info + } + if other.View != nil { + view := *other.View + c.View = &view + } + if other.Config != nil { + cfg := *other.Config + c.Config = &cfg + } +} + +// mergeComplexFields копирует сложные поля из other коллекции в текущую коллекцию. +func (c *Collection) mergeComplexFields(other *Collection) { + if other.Schema != nil { + c.Schema = other.Schema.Clone(false) + } + if other.Access != nil { + c.Access = other.Access.Clone() + } + if other.Tags != nil { + tags := make([]string, len(other.Tags)) + copy(tags, other.Tags) + c.Tags = tags + } + if len(other.Translations) > 0 { + translations := make(map[string]map[string]string, len(other.Translations)) + for k, v := range other.Translations { + translations[k] = maps.Clone(v) + } + c.Translations = translations + } +} + +// Merge объединяет две коллекции, возвращая новую коллекцию +// с объединенными данными. Если данные второй коллекции не пустые, +// то данные из второй коллекции перезаписывают данные из первой. +func Merge(c1, c2 *Collection) *Collection { + return c1.Merge(c2) +} diff --git a/pkg/collections/collection_test.go b/pkg/collections/collection_test.go index 79e16a8e6a397418ef2418979bf0e38947b730c8..84e0d5e509a256e6464e8e20ad9023109e97beb8 100644 --- a/pkg/collections/collection_test.go +++ b/pkg/collections/collection_test.go @@ -2,7 +2,9 @@ package collections import ( "testing" + "time" + "git.perx.ru/perxis/perxis-go/pkg/schema" "github.com/stretchr/testify/require" ) @@ -100,3 +102,225 @@ func TestView_Equal(t *testing.T) { }) } } + +func Test_Merge(t *testing.T) { + boolTrue := true + boolFalse := false + + // Create schema objects with Title field + schema1 := schema.New() + schema1.Title = "schema1" + + schema2 := schema.New() + schema2.Title = "schema2" + + testCases := []struct { + name string + c1 *Collection + c2 *Collection + want *Collection + }{ + { + name: "Both collections are nil", + c1: nil, + c2: nil, + want: nil, + }, + { + name: "First collection is nil", + c1: nil, + c2: &Collection{ + ID: "id2", + Name: "name2", + }, + want: &Collection{ + ID: "id2", + Name: "name2", + }, + }, + { + name: "Second collection is nil", + c1: &Collection{ + ID: "id1", + Name: "name1", + }, + c2: nil, + want: &Collection{ + ID: "id1", + Name: "name1", + }, + }, + { + name: "Merge primitive fields", + c1: &Collection{ + ID: "id1", + SpaceID: "space1", + EnvID: "env1", + Name: "name1", + Hidden: false, + NoPublish: false, + NoArchive: false, + NoRevisions: false, + MaxRevisions: 5, + RevisionTTL: time.Hour, + }, + c2: &Collection{ + ID: "id2", + SpaceID: "space2", + EnvID: "env2", + Name: "name2", + Hidden: true, + NoPublish: true, + NoArchive: true, + NoRevisions: true, + MaxRevisions: 10, + RevisionTTL: time.Hour * 2, + }, + want: &Collection{ + ID: "id2", + SpaceID: "space2", + EnvID: "env2", + Name: "name2", + Hidden: true, + NoPublish: true, + NoArchive: true, + NoRevisions: true, + MaxRevisions: 10, + RevisionTTL: time.Hour * 2, + }, + }, + { + name: "Merge pointer fields", + c1: &Collection{ + NoData: &boolFalse, + Single: &boolFalse, + System: &boolFalse, + }, + c2: &Collection{ + NoData: &boolTrue, + Single: &boolTrue, + System: &boolTrue, + }, + want: &Collection{ + NoData: &boolTrue, + Single: &boolTrue, + System: &boolTrue, + }, + }, + { + name: "Merge complex fields", + c1: &Collection{ + Schema: schema1, + View: &View{ + SpaceID: "space1", + EnvID: "env1", + CollectionID: "collection1", + Filter: "filter1", + }, + Tags: []string{"tag1", "tag2"}, + Translations: map[string]map[string]string{ + "en": { + "key1": "value1", + }, + }, + }, + c2: &Collection{ + Schema: schema2, + View: &View{ + SpaceID: "space2", + EnvID: "env2", + CollectionID: "collection2", + Filter: "filter2", + }, + Tags: []string{"tag3", "tag4"}, + Translations: map[string]map[string]string{ + "fr": { + "key2": "value2", + }, + }, + }, + want: &Collection{ + Schema: schema2, + View: &View{ + SpaceID: "space2", + EnvID: "env2", + CollectionID: "collection2", + Filter: "filter2", + }, + Tags: []string{"tag3", "tag4"}, + Translations: map[string]map[string]string{ + "fr": { + "key2": "value2", + }, + }, + }, + }, + { + name: "Merge with empty second collection", + c1: &Collection{ + ID: "id1", + SpaceID: "space1", + EnvID: "env1", + Name: "name1", + NoData: &boolTrue, + Single: &boolTrue, + System: &boolTrue, + Schema: schema1, + Tags: []string{"tag1", "tag2"}, + }, + c2: &Collection{}, + want: &Collection{ + ID: "id1", + SpaceID: "space1", + EnvID: "env1", + Name: "name1", + NoData: &boolTrue, + Single: &boolTrue, + System: &boolTrue, + Schema: schema1, + Tags: []string{"tag1", "tag2"}, + }, + }, + { + name: "Merge with partial second collection", + c1: &Collection{ + ID: "id1", + SpaceID: "space1", + EnvID: "env1", + Name: "name1", + NoData: &boolFalse, + Single: &boolFalse, + System: &boolFalse, + Schema: schema1, + Tags: []string{"tag1", "tag2"}, + }, + c2: &Collection{ + Name: "name2", + NoData: &boolTrue, + Schema: schema2, + }, + want: &Collection{ + ID: "id1", + SpaceID: "space1", + EnvID: "env1", + Name: "name2", + NoData: &boolTrue, + Single: &boolFalse, + System: &boolFalse, + Schema: schema2, + Tags: []string{"tag1", "tag2"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := Merge(tc.c1, tc.c2) + + require.True(t, IsEqual(result, tc.want)) + if result != nil && tc.want != nil { + require.True(t, schema.IsEqual(result.Schema, tc.want.Schema)) + } + }) + } +} diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 7b337f8ab68f732fb3ab2ac804fe89e082c58c6a..bdebe4f04025278d00ec41b20bba2b596ac5de8a 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -48,14 +48,24 @@ func (s *Schema) ClearState() *Schema { return s } -func (s *Schema) Equal(sch *Schema) bool { - if s == sch { +// Equal compares two schemas for equality. +// Deprecated: use `IsEqual` method instead. +func (s *Schema) Equal(other *Schema) bool { + return s.IsEqual(other) +} + +func (s *Schema) IsEqual(other *Schema) bool { + if s == other { return true } - if s == nil || sch == nil { + if s == nil || other == nil { return false } - return reflect.DeepEqual(s.Field, sch.Field) + return reflect.DeepEqual(s.Field, other.Field) +} + +func IsEqual(s1, s2 *Schema) bool { + return s1.IsEqual(s2) } func (s Schema) WithIncludes(includes ...interface{}) *Schema { @@ -78,9 +88,9 @@ func (s Schema) SetMetadata(md map[string]string) *Schema { return &s } -func (f Schema) SetSingleLocale(r bool) *Schema { - f.SingleLocale = r - return &f +func (s Schema) SetSingleLocale(r bool) *Schema { + s.SingleLocale = r + return &s } func (s *Schema) ConvertTypes() error {