From 3cfdbe6f8d90fc8bc9a382fabbc08441ec7b2abc Mon Sep 17 00:00:00 2001 From: akiyatomohiro <83905169+akiyatomohiro@users.noreply.github.com> Date: Thu, 29 Feb 2024 19:26:28 +0900 Subject: [PATCH] feat(server): Create List as a generic pkg (#39) --- util/list.go | 343 +++++++++++++ util/list_test.go | 1200 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1543 insertions(+) diff --git a/util/list.go b/util/list.go index d6d6df0e..b96796c5 100644 --- a/util/list.go +++ b/util/list.go @@ -7,6 +7,19 @@ import ( type List[T comparable] []T +type Identifiable[ID comparable] interface { + ID() ID +} + +type IDLister[ID comparable] interface { + LayerCount() int + Layers() []ID +} + +type Converter[S any, T any] func(*S) *T + +type ConverterValue[S any, T any] func(S) *T + func (l List[T]) Has(elements ...T) bool { return Any(elements, func(e T) bool { return slices.Contains(l, e) @@ -128,3 +141,333 @@ func (l List[T]) Intersect(m []T) List[T] { } return lo.Intersect(m, l) } + +func Last[T any](list []*T) *T { + if len(list) == 0 { + return nil + } + return list[len(list)-1] +} + +func ExtractIDs[ID comparable, T Identifiable[ID]](list []*T) []ID { + if len(list) == 0 { + return nil + } + ids := make([]ID, 0, len(list)) + for _, item := range Deref[T](list, false) { + ids = append(ids, item.ID()) + } + return ids +} + +func Pick[ID comparable, T Identifiable[ID]](list []*T, idList IDLister[ID]) []*T { + if idList == nil || idList.LayerCount() == 0 { + return nil + } + + layers := make([]*T, 0, idList.LayerCount()) + for _, lid := range idList.Layers() { + if l := Find[ID, T](list, lid); l != nil { + layers = append(layers, l) + } + } + return layers +} + +func Find[ID comparable, T Identifiable[ID]](list []*T, lid ID) *T { + for _, item := range list { + if item == nil { + continue + } + if (*item).ID() == lid { + return item + } + } + return nil +} + +func Deref[T any](list []*T, skipNil bool) []T { + if !skipNil && list == nil { + return nil + } + res := make([]T, 0, len(list)) + for _, item := range list { + if item == nil { + if !skipNil { + var zeroValue T + res = append(res, zeroValue) + } + continue + } + res = append(res, *item) + } + return res +} + +func MapAdd[ID comparable, T Identifiable[ID]](m map[ID]*T, items ...*T) map[ID]*T { + if m == nil { + m = map[ID]*T{} + } + for _, item := range items { + if item == nil { + continue + } + m[(*item).ID()] = item + } + return m +} + +func ListMap[ID comparable, T Identifiable[ID]](list []*T) map[ID]*T { + m := make(map[ID]*T, len(list)) + MapAdd[ID, T](m, list...) + return m +} + +func MapWithIDFunc[ID comparable, T any](list []*T, idFunc func(*T) ID, checkNil bool) map[ID]*T { + if checkNil && list == nil { + return nil + } + m := make(map[ID]*T, len(list)) + for _, item := range list { + if item != nil { + id := idFunc(item) + m[id] = item + } + } + return m +} + +func Merge[ID comparable, T Identifiable[ID]](m map[ID]*T, m2 map[ID]*T) map[ID]*T { + if m == nil { + return Clone[ID, T](m2) + } + m3 := Clone[ID, T](m) + if m2 == nil { + return m3 + } + + return MapAdd[ID, T](m3, MapList[ID, T](m2, false)...) +} + +func ListMerge[T comparable](list []T, list2 []T, getClone func(T) T, duplicateSkip bool) []T { + result := make([]T, 0, len(list)+len(list2)) + + for _, item := range list { + result = append(result, getClone(item)) + } + + for _, item := range list2 { + if duplicateSkip { + if !Contains[T](result, item) { + result = append(result, getClone(item)) + } + } else { + result = append(result, getClone(item)) + } + } + + return result +} + +func MapList[ID comparable, T any](m map[ID]*T, skipNil bool) []*T { + if m == nil { + return nil + } + list := make([]*T, 0, len(m)) + for _, l := range m { + if !skipNil || l != nil { + list = append(list, l) + } + } + return list +} + +func Clone[ID comparable, T any](m map[ID]*T) map[ID]*T { + if m == nil { + return map[ID]*T{} + } + m2 := make(map[ID]*T, len(m)) + for k, v := range m { + m2[k] = v + } + return m2 +} + +func ListClone[T any](list []T, getClone func(T) T) []T { + if list == nil { + return nil + } + list2 := make([]T, len(list)) + for i, item := range list { + list2[i] = getClone(item) + } + return list2 +} + +func Remove[ID comparable, T Identifiable[ID]](list []*T, idsToRemove ...ID) []*T { + if list == nil { + return nil + } + if len(list) == 0 { + return []*T{} + } + + result := make([]*T, 0, len(list)) + for _, item := range list { + remove := false + for _, id := range idsToRemove { + if (*item).ID() == id { + remove = true + break + } + } + if !remove { + result = append(result, item) + } + } + return result +} + +func AddUnique[ID comparable, T Identifiable[ID]](list []*T, newList []*T) []*T { + res := append([]*T{}, list...) + + for _, l := range newList { + if l == nil { + continue + } + if Find[ID, T](res, (*l).ID()) != nil { + continue + } + res = append(res, l) + } + + return res +} + +func MapPick[ID comparable, T Identifiable[ID]](m map[ID]*T, idList IDLister[ID]) []*T { + if idList == nil || idList.LayerCount() == 0 { + return nil + } + + layers := make([]*T, 0, idList.LayerCount()) + for _, lid := range idList.Layers() { + if l := m[lid]; l != nil { + layers = append(layers, l) + } + } + return layers +} + +func ExtractKeys[ID comparable, T any](m map[ID]*T) []ID { + keys := make([]ID, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + +func ToGenericList[S any, T any](list []*S, converter Converter[S, T]) []*T { + res := make([]*T, 0, len(list)) + for _, l := range list { + if li := converter(l); li != nil { + res = append(res, li) + } + } + return res +} + +func ToGenericListValue[S any, T any](list []S, converter ConverterValue[S, T]) []*T { + if len(list) == 0 { + return nil + } + res := make([]*T, 0, len(list)) + for _, l := range list { + if li := converter(l); li != nil { + res = append(res, li) + } + } + return res +} + +func ListHas[ID comparable, T any](list []*T, getId func(*T) ID, id ID) bool { + for _, item := range list { + if getId(item) == id { + return true + } + } + return false +} + +func Get[ID comparable, T any](list []*T, getId func(*T) ID, id ID) *T { + for _, item := range list { + if getId(item) == id { + return item + } + } + return nil +} + +func RemoveById[ID comparable, T any](list []*T, getId func(*T) ID, id ID) []*T { + for index, item := range list { + if getId(item) == id { + list = append(list[:index], list[index+1:]...) + return list + } + } + return list +} + +func RemoveByIds[ID comparable, T any](list []*T, getId func(*T) ID, ids ...ID) []*T { + result := make([]*T, 0, len(list)) + for _, item := range list { + itemID := getId(item) + if !Contains[ID](ids, itemID) { + result = append(result, item) + } + } + return result +} + +func Contains[ID comparable](ids []ID, id ID) bool { + for _, i := range ids { + if i == id { + return true + } + } + return false +} + +func Properties[ID comparable, T any](list []*T, getProperty func(*T) ID) []ID { + if list == nil { + return nil + } + ids := make([]ID, 0, len(list)) + for _, item := range list { + if item != nil { + ids = append(ids, getProperty(item)) + } + } + return ids +} + +func ListFilter[ID comparable, T any](list []T, id ID, getId func(T) ID) []T { + if len(list) == 0 { + return nil + } + res := make([]T, 0, len(list)) + for _, item := range list { + if getId(item) == id { + res = append(res, item) + } + } + return res +} + +func IndexOf[ID comparable, T any](list []*T, getId func(*T) ID, id ID) int { + for index, item := range list { + if getId(item) == id { + return index + } + } + return -1 +} diff --git a/util/list_test.go b/util/list_test.go index e54db50d..eff3c140 100644 --- a/util/list_test.go +++ b/util/list_test.go @@ -8,6 +8,26 @@ import ( type T struct{} +type MockIdentifiable struct { + id string +} + +func (m MockIdentifiable) ID() string { + return m.id +} + +type MockIDLister struct { + ids []string +} + +func (m MockIDLister) LayerCount() int { + return len(m.ids) +} + +func (m MockIDLister) Layers() []string { + return m.ids +} + func TestList_Has(t *testing.T) { l := List[int]{1, 2} @@ -166,3 +186,1183 @@ func TestList_Intersect(t *testing.T) { assert.Equal(t, List[string]{"a", "b"}, l.Intersect(List[string]{"b", "e", "a"})) assert.Equal(t, List[string]{"a", "b", "c"}, l) } + +func TestLast(t *testing.T) { + int1 := 1 + int2 := 2 + + tests := []struct { + name string + target []*int + want *int + }{ + { + name: "last element", + target: []*int{&int1, &int2}, + want: &int2, + }, + { + name: "empty slice", + target: []*int{}, + want: nil, + }, + { + name: "nil slice", + target: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, Last[int](tt.target)) + }) + } +} + +func TestExtractIDs(t *testing.T) { + tests := []struct { + name string + target []*MockIdentifiable + want []string + }{ + { + name: "normal case", + target: []*MockIdentifiable{{id: "id1"}, {id: "id2"}, {id: "id3"}}, + want: []string{"id1", "id2", "id3"}, + }, + { + name: "empty slice", + target: []*MockIdentifiable{}, + want: nil, + }, + { + name: "nil contained", + target: []*MockIdentifiable{{id: "id1"}, nil, {id: "id3"}}, + want: []string{"id1", "", "id3"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ExtractIDs[string, MockIdentifiable](tt.target) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestPick(t *testing.T) { + tests := []struct { + name string + slice []*MockIdentifiable + idList MockIDLister + want []*MockIdentifiable + }{ + { + name: "pick existing items", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}, {id: "id3"}}, + idList: MockIDLister{ids: []string{"id1", "id3"}}, + want: []*MockIdentifiable{{id: "id1"}, {id: "id3"}}, + }, + { + name: "pick non-existing item", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + idList: MockIDLister{ids: []string{"id3"}}, + want: []*MockIdentifiable{}, + }, + { + name: "nil id list", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + idList: MockIDLister{ids: nil}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Pick[string, MockIdentifiable](tt.slice, tt.idList) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestFind(t *testing.T) { + tests := []struct { + name string + slice []*MockIdentifiable + lid string + want *MockIdentifiable + }{ + { + name: "find existing item", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + lid: "id2", + want: &MockIdentifiable{id: "id2"}, + }, + { + name: "find non-existing item", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + lid: "id3", + want: nil, + }, + { + name: "nil item in slice", + slice: []*MockIdentifiable{{id: "id1"}, nil, {id: "id3"}}, + lid: "id3", + want: &MockIdentifiable{id: "id3"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Find[string, MockIdentifiable](tt.slice, tt.lid) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestDeref(t *testing.T) { + tests := []struct { + name string + slice []*MockIdentifiable + skipNil bool + want []MockIdentifiable + }{ + { + name: "non-empty slice", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + skipNil: false, + want: []MockIdentifiable{{id: "id1"}, {id: "id2"}}, + }, + { + name: "empty slice", + slice: []*MockIdentifiable{}, + skipNil: false, + want: []MockIdentifiable{}, + }, + { + name: "nil slice", + slice: nil, + skipNil: false, + want: nil, + }, + { + name: "slice with nil element", + slice: []*MockIdentifiable{{id: "id1"}, nil, {id: "id3"}}, + skipNil: false, + want: []MockIdentifiable{{id: "id1"}, {id: ""}, {id: "id3"}}, + }, + { + name: "nil slice with skipNil true", + slice: nil, + skipNil: true, + want: []MockIdentifiable{}, + }, + { + name: "slice with nil element with skipNil true", + slice: []*MockIdentifiable{{id: "id1"}, nil, {id: "id3"}}, + skipNil: true, + want: []MockIdentifiable{{id: "id1"}, {id: "id3"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Deref[MockIdentifiable](tt.slice, tt.skipNil) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMapAdd(t *testing.T) { + tests := []struct { + name string + m map[string]*MockIdentifiable + items []*MockIdentifiable + want map[string]*MockIdentifiable + }{ + { + name: "add to empty map", + m: make(map[string]*MockIdentifiable), + items: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + }, + { + name: "add to non-empty map", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + items: []*MockIdentifiable{{id: "id2"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + }, + { + name: "nil item", + m: make(map[string]*MockIdentifiable), + items: []*MockIdentifiable{nil}, + want: map[string]*MockIdentifiable{}, + }, + { + name: "nil map", + m: nil, + items: []*MockIdentifiable{{id: "id1"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MapAdd[string, MockIdentifiable](tt.m, tt.items...) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestListMap(t *testing.T) { + tests := []struct { + name string + slice []*MockIdentifiable + want map[string]*MockIdentifiable + }{ + { + name: "non-empty slice", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + }, + { + name: "empty slice", + slice: []*MockIdentifiable{}, + want: map[string]*MockIdentifiable{}, + }, + { + name: "nil slice", + slice: nil, + want: map[string]*MockIdentifiable{}, + }, + { + name: "slice with nil element", + slice: []*MockIdentifiable{{id: "id1"}, nil, {id: "id3"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id3": {id: "id3"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ListMap[string, MockIdentifiable](tt.slice) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMapWithIDFunc(t *testing.T) { + idFunc := func(item *MockIdentifiable) string { + if item != nil { + return item.ID() + } + return "" + } + + tests := []struct { + name string + slice []*MockIdentifiable + checkNil bool + want map[string]*MockIdentifiable + }{ + { + name: "non-empty slice", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + checkNil: false, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + }, + { + name: "nil slice with checkNil true", + slice: nil, + checkNil: true, + want: nil, + }, + { + name: "nil slice with checkNil false", + slice: nil, + checkNil: false, + want: map[string]*MockIdentifiable{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MapWithIDFunc[string, MockIdentifiable](tt.slice, idFunc, tt.checkNil) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMerge(t *testing.T) { + tests := []struct { + name string + m map[string]*MockIdentifiable + m2 map[string]*MockIdentifiable + want map[string]*MockIdentifiable + }{ + { + name: "merge two non-empty maps", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + m2: map[string]*MockIdentifiable{"id2": {id: "id2"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + }, + { + name: "merge with nil map", + m: nil, + m2: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + }, + { + name: "merge with nil map 2", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + m2: nil, + want: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + }, + { + name: "merge with nil maps", + m: nil, + m2: nil, + want: map[string]*MockIdentifiable{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Merge[string, MockIdentifiable](tt.m, tt.m2) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestListMerge(t *testing.T) { + type T struct { + ID string + Name string + } + cloneFunc := func(item T) T { + return T{ID: item.ID, Name: item.Name} + } + + tests := []struct { + name string + list1 []T + list2 []T + getClone func(T) T + duplicateSkip bool + want []T + }{ + { + name: "merge two non-empty lists", + list1: []T{{ID: "id1", Name: "Item 1"}, {ID: "id3", Name: "Item 3"}}, + list2: []T{{ID: "id2", Name: "Item 2"}, {ID: "id4", Name: "Item 4"}}, + getClone: cloneFunc, + duplicateSkip: false, + want: []T{{ID: "id1", Name: "Item 1"}, {ID: "id3", Name: "Item 3"}, {ID: "id2", Name: "Item 2"}, {ID: "id4", Name: "Item 4"}}, + }, + { + name: "merge two non-empty lists with duplicate items", + list1: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}, {ID: "id3", Name: "Item 3"}}, + list2: []T{{ID: "id2", Name: "Item 2"}, {ID: "id4", Name: "Item 4"}}, + getClone: cloneFunc, + duplicateSkip: false, + want: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}, {ID: "id3", Name: "Item 3"}, {ID: "id2", Name: "Item 2"}, {ID: "id4", Name: "Item 4"}}, + }, + { + name: "merge two non-empty lists with duplicate items and skip", + list1: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}, {ID: "id3", Name: "Item 3"}}, + list2: []T{{ID: "id2", Name: "Item 2"}, {ID: "id4", Name: "Item 4"}}, + getClone: cloneFunc, + duplicateSkip: true, + want: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}, {ID: "id3", Name: "Item 3"}, {ID: "id4", Name: "Item 4"}}, + }, + { + name: "merge non-empty list with empty list", + list1: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}}, + list2: []T{}, + getClone: cloneFunc, + duplicateSkip: true, + want: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}}, + }, + { + name: "merge empty list with non-empty list", + list1: []T{}, + list2: []T{{ID: "id3", Name: "Item 3"}, {ID: "id4", Name: "Item 4"}}, + getClone: cloneFunc, + duplicateSkip: false, + want: []T{{ID: "id3", Name: "Item 3"}, {ID: "id4", Name: "Item 4"}}, + }, + { + name: "merge two nil lists", + list1: nil, + list2: nil, + duplicateSkip: true, + getClone: cloneFunc, + want: []T{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ListMerge(tt.list1, tt.list2, tt.getClone, tt.duplicateSkip) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMapList(t *testing.T) { + tests := []struct { + name string + m map[string]*MockIdentifiable + skipNil bool + want []*MockIdentifiable + }{ + { + name: "non-empty map", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + skipNil: false, + want: []*MockIdentifiable{{id: "id1"}}, + }, + { + name: "map with nil values", + m: map[string]*MockIdentifiable{"id1": nil, "id2": {id: "id2"}}, + skipNil: true, + want: []*MockIdentifiable{{id: "id2"}}, + }, + { + name: "empty map", + m: map[string]*MockIdentifiable{}, + skipNil: false, + want: []*MockIdentifiable{}, + }, + { + name: "nil map", + m: nil, + skipNil: false, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MapList[string, MockIdentifiable](tt.m, tt.skipNil) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClone(t *testing.T) { + tests := []struct { + name string + m map[string]*MockIdentifiable + want map[string]*MockIdentifiable + }{ + { + name: "clone non-empty map", + m: map[string]*MockIdentifiable{ + "id1": {id: "id1"}, + "id2": {id: "id2"}, + }, + want: map[string]*MockIdentifiable{ + "id1": {id: "id1"}, + "id2": {id: "id2"}, + }, + }, + { + name: "clone empty map", + m: map[string]*MockIdentifiable{}, + want: map[string]*MockIdentifiable{}, + }, + { + name: "clone nil map", + m: nil, + want: map[string]*MockIdentifiable{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Clone[string, MockIdentifiable](tt.m) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestListClone(t *testing.T) { + type T struct { + ID string + Name string + } + cloneFunc := func(item T) T { + return T{ID: item.ID, Name: item.Name} + } + + tests := []struct { + name string + list []T + getClone func(T) T + want []T + }{ + { + name: "clone non-empty list", + list: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}}, + getClone: cloneFunc, + want: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}}, + }, + { + name: "clone empty list", + list: []T{}, + getClone: cloneFunc, + want: []T{}, + }, + { + name: "clone nil list", + list: nil, + getClone: cloneFunc, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ListClone(tt.list, tt.getClone) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRemove(t *testing.T) { + tests := []struct { + name string + slice []*MockIdentifiable + idsToRemove []string + want []*MockIdentifiable + }{ + { + name: "remove existing items", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}, {id: "id3"}}, + idsToRemove: []string{"id1", "id3"}, + want: []*MockIdentifiable{{id: "id2"}}, + }, + { + name: "remove non-existing items", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + idsToRemove: []string{"id3"}, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + }, + { + name: "remove all items", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + idsToRemove: []string{"id1", "id2"}, + want: []*MockIdentifiable{}, + }, + { + name: "remove from empty slice", + slice: []*MockIdentifiable{}, + idsToRemove: []string{"id1", "id2"}, + want: []*MockIdentifiable{}, + }, + { + name: "remove from nil slice", + slice: nil, + idsToRemove: []string{"id1", "id2"}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Remove[string, MockIdentifiable](tt.slice, tt.idsToRemove...) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAddUnique(t *testing.T) { + tests := []struct { + name string + slice []*MockIdentifiable + newList []*MockIdentifiable + want []*MockIdentifiable + }{ + { + name: "add unique items", + slice: []*MockIdentifiable{{id: "id1"}}, + newList: []*MockIdentifiable{{id: "id2"}, {id: "id3"}}, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}, {id: "id3"}}, + }, + { + name: "skip existing items", + slice: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + newList: []*MockIdentifiable{{id: "id2"}, {id: "id3"}}, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}, {id: "id3"}}, + }, + { + name: "add to empty slice", + slice: []*MockIdentifiable{}, + newList: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + }, + { + name: "add to nil slice", + slice: nil, + newList: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + }, + { + name: "add nil item", + slice: []*MockIdentifiable{{id: "id1"}}, + newList: []*MockIdentifiable{nil}, + want: []*MockIdentifiable{{id: "id1"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := AddUnique[string, MockIdentifiable](tt.slice, tt.newList) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestMapPick(t *testing.T) { + tests := []struct { + name string + m map[string]*MockIdentifiable + idList MockIDLister + want []*MockIdentifiable + }{ + { + name: "pick existing items from map", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + idList: MockIDLister{ids: []string{"id1"}}, + want: []*MockIdentifiable{{id: "id1"}}, + }, + { + name: "pick non-existing items from map", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + idList: MockIDLister{ids: []string{"id3"}}, + want: []*MockIdentifiable{}, + }, + { + name: "nil id list", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}, "id2": {id: "id2"}}, + idList: MockIDLister{ids: nil}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MapPick[string, MockIdentifiable](tt.m, tt.idList) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestExtractKeys(t *testing.T) { + tests := []struct { + name string + m map[string]*MockIdentifiable + want []string + }{ + { + name: "non-empty map", + m: map[string]*MockIdentifiable{"id1": {id: "id1"}}, + want: []string{"id1"}, + }, + { + name: "empty map", + m: map[string]*MockIdentifiable{}, + want: []string{}, + }, + { + name: "nil map", + m: nil, + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ExtractKeys[string, MockIdentifiable](tt.m) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestToGenericList(t *testing.T) { + converter := func(s *MockIdentifiable) *MockIdentifiable { + return s + } + + tests := []struct { + name string + list []*MockIdentifiable + converter func(*MockIdentifiable) *MockIdentifiable + want []*MockIdentifiable + }{ + { + name: "non-empty list", + list: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + converter: converter, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + }, + { + name: "empty list", + list: []*MockIdentifiable{}, + converter: converter, + want: []*MockIdentifiable{}, + }, + { + name: "nil list", + list: nil, + converter: converter, + want: []*MockIdentifiable{}, + }, + { + name: "list with nil element", + list: []*MockIdentifiable{{id: "id1"}, nil, {id: "id3"}}, + converter: converter, + want: []*MockIdentifiable{{id: "id1"}, {id: "id3"}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ToGenericList[MockIdentifiable, MockIdentifiable](tt.list, tt.converter) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestToGenericListValue(t *testing.T) { + converter := func(s MockIdentifiable) *MockIdentifiable { + return &s + } + + tests := []struct { + name string + list []MockIdentifiable + converter func(MockIdentifiable) *MockIdentifiable + want []*MockIdentifiable + }{ + { + name: "non-empty list", + list: []MockIdentifiable{{id: "id1"}, {id: "id2"}}, + converter: converter, + want: []*MockIdentifiable{{id: "id1"}, {id: "id2"}}, + }, + { + name: "empty list", + list: []MockIdentifiable{}, + converter: converter, + want: nil, + }, + { + name: "nil list", + list: nil, + converter: converter, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ToGenericListValue[MockIdentifiable, MockIdentifiable](tt.list, tt.converter) + assert.Equal(t, tt.want, got) + }) + } +} + +func ListTestHas(t *testing.T) { + type ID string + type T struct { + ID ID + } + getID := func(t *T) ID { + return t.ID + } + + tests := []struct { + name string + list []*T + id ID + want bool + }{ + { + name: "existing item", + list: []*T{{ID: "id1"}, {ID: "id2"}, {ID: "id3"}}, + id: "id2", + want: true, + }, + { + name: "non-existing item", + list: []*T{{ID: "id1"}, {ID: "id2"}}, + id: "id3", + want: false, + }, + { + name: "empty list", + list: []*T{}, + id: "id1", + want: false, + }, + { + name: "nil list", + list: nil, + id: "id1", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ListHas[ID, T](tt.list, getID, tt.id) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGet(t *testing.T) { + type ID string + type T struct { + ID ID + } + getID := func(t *T) ID { + return t.ID + } + + tests := []struct { + name string + list []*T + id ID + want *T + }{ + { + name: "existing item", + list: []*T{{ID: "id1"}, {ID: "id2"}, {ID: "id3"}}, + id: "id2", + want: &T{ID: "id2"}, + }, + { + name: "non-existing item", + list: []*T{{ID: "id1"}, {ID: "id2"}}, + id: "id3", + want: nil, + }, + { + name: "empty list", + list: []*T{}, + id: "id1", + want: nil, + }, + { + name: "nil list", + list: nil, + id: "id1", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Get[ID, T](tt.list, getID, tt.id) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRemoveById(t *testing.T) { + type ID string + type T struct { + ID ID + } + getID := func(t *T) ID { + return t.ID + } + + tests := []struct { + name string + list []*T + id ID + want []*T + }{ + { + name: "remove existing items", + list: []*T{{ID: "id1"}, {ID: "id2"}, {ID: "id3"}}, + id: ID("id1"), + want: []*T{{ID: "id2"}, {ID: "id3"}}, + }, + { + name: "remove non-existing items", + list: []*T{{ID: "id1"}, {ID: "id2"}}, + id: ID("id3"), + want: []*T{{ID: "id1"}, {ID: "id2"}}, + }, + { + name: "remove duplicate items", + list: []*T{{ID: "id1"}, {ID: "id1"}, {ID: "id2"}}, + id: ID("id1"), + want: []*T{{ID: "id1"}, {ID: "id2"}}, + }, + { + name: "remove from empty slice", + list: []*T{}, + id: ID("id1"), + want: []*T{}, + }, + { + name: "remove from nil slice", + list: nil, + id: ID("id1"), + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := RemoveById[ID, T](tt.list, getID, tt.id) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRemoveByIds(t *testing.T) { + type ID string + type T struct { + ID ID + } + getID := func(t *T) ID { + return t.ID + } + + tests := []struct { + name string + list []*T + ids []ID + want []*T + }{ + { + name: "remove existing items", + list: []*T{{ID: "id1"}, {ID: "id2"}, {ID: "id3"}}, + ids: []ID{"id1", "id3"}, + want: []*T{{ID: "id2"}}, + }, + { + name: "remove non-existing items", + list: []*T{{ID: "id1"}, {ID: "id2"}}, + ids: []ID{"id3"}, + want: []*T{{ID: "id1"}, {ID: "id2"}}, + }, + { + name: "remove all items", + list: []*T{{ID: "id1"}, {ID: "id2"}}, + ids: []ID{"id1", "id2"}, + want: []*T{}, + }, + { + name: "remove from empty slice", + list: []*T{}, + ids: []ID{"id1", "id2"}, + want: []*T{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := RemoveByIds[ID, T](tt.list, getID, tt.ids...) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestContains(t *testing.T) { + type ID string + + tests := []struct { + name string + ids []ID + id ID + want bool + }{ + { + name: "string slice contains id", + ids: []ID{"id1", "id2", "id3"}, + id: "id2", + want: true, + }, + { + name: "string slice does not contain id", + ids: []ID{"id1", "id2", "id3"}, + id: "id4", + want: false, + }, + { + name: "nil slice", + ids: nil, + id: "id1", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Contains[ID](tt.ids, tt.id) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestProperties(t *testing.T) { + type PropertyID string + type T struct { + PropertyID PropertyID + } + getProperty := func(t *T) PropertyID { + return t.PropertyID + } + + tests := []struct { + name string + list []*T + want []PropertyID + }{ + { + name: "non-empty list", + list: []*T{{PropertyID: "PropertyID1"}, {PropertyID: "PropertyID2"}}, + want: []PropertyID{"PropertyID1", "PropertyID2"}, + }, + { + name: "empty list", + list: []*T{}, + want: []PropertyID{}, + }, + { + name: "nil list", + list: nil, + want: nil, + }, + { + name: "list with nil element", + list: []*T{{PropertyID: "PropertyID1"}, nil, {PropertyID: "PropertyID3"}}, + want: []PropertyID{"PropertyID1", "PropertyID3"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Properties[PropertyID, T](tt.list, getProperty) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestListFilter(t *testing.T) { + type ID string + type T struct { + ID ID + Name string + } + getID := func(t T) ID { + return t.ID + } + + tests := []struct { + name string + list []T + id ID + getId func(T) ID + want []T + }{ + { + name: "filter existing items by ID", + list: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}, {ID: "id1", Name: "Item 3"}}, + id: "id1", + getId: getID, + want: []T{{ID: "id1", Name: "Item 1"}, {ID: "id1", Name: "Item 3"}}, + }, + { + name: "filter non-existing item by ID", + list: []T{{ID: "id1", Name: "Item 1"}, {ID: "id2", Name: "Item 2"}}, + id: "id3", + getId: getID, + want: []T{}, + }, + { + name: "empty list", + list: []T{}, + id: "id1", + getId: getID, + want: nil, + }, + { + name: "nil list", + list: nil, + id: "id1", + getId: getID, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ListFilter(tt.list, tt.id, tt.getId) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestIndexOf(t *testing.T) { + type ID string + type T struct { + ID ID + } + getID := func(t *T) ID { + return t.ID + } + + tests := []struct { + name string + list []*T + id ID + want int + }{ + { + name: "existing item", + list: []*T{{ID: "id1"}, {ID: "id2"}, {ID: "id3"}}, + id: ID("id1"), + want: 0, + }, + { + name: "existing item 2", + list: []*T{{ID: "id1"}, {ID: "id2"}, {ID: "id3"}}, + id: ID("id3"), + want: 2, + }, + { + name: "non-existing item", + list: []*T{{ID: "id1"}, {ID: "id2"}}, + id: ID("id3"), + want: -1, + }, + { + name: "empty slice", + list: []*T{}, + id: ID("id1"), + want: -1, + }, + { + name: "nil slice", + list: nil, + id: ID("id1"), + want: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := IndexOf[ID, T](tt.list, getID, tt.id) + assert.Equal(t, tt.want, got) + }) + } +}