Other: Safer user types

This commit is contained in:
James Houlahan
2022-10-12 00:20:04 +02:00
parent 4dc32dc7f2
commit fd63611b41
35 changed files with 1253 additions and 771 deletions

View File

@ -3,18 +3,26 @@ package safe
import (
"sync"
"golang.org/x/exp/maps"
"github.com/bradenaw/juniper/xslices"
"golang.org/x/exp/slices"
)
type Map[Key comparable, Val any] struct {
data map[Key]Val
lock sync.RWMutex
data map[Key]Val
order []Key
sort func(a, b Key, data map[Key]Val) bool
lock sync.RWMutex
}
func NewMap[Key comparable, Val any](from map[Key]Val) *Map[Key, Val] {
m := &Map[Key, Val]{
func NewMap[Key comparable, Val any](sort func(a, b Key, data map[Key]Val) bool) *Map[Key, Val] {
return &Map[Key, Val]{
data: make(map[Key]Val),
sort: sort,
}
}
func NewMapFrom[Key comparable, Val any](from map[Key]Val, sort func(a, b Key, data map[Key]Val) bool) *Map[Key, Val] {
m := NewMap(sort)
for key, val := range from {
m.Set(key, val)
@ -23,12 +31,36 @@ func NewMap[Key comparable, Val any](from map[Key]Val) *Map[Key, Val] {
return m
}
func (m *Map[Key, Val]) Has(key Key) bool {
func (m *Map[Key, Val]) Index(idx int, fn func(Key, Val)) bool {
m.lock.RLock()
defer m.lock.RUnlock()
_, ok := m.data[key]
return ok
if idx < 0 || idx >= len(m.order) {
return false
}
fn(m.order[idx], m.data[m.order[idx]])
return true
}
func (m *Map[Key, Val]) Has(key Key) bool {
return m.HasFunc(func(k Key, v Val) bool {
return k == key
})
}
func (m *Map[Key, Val]) HasFunc(fn func(key Key, val Val) bool) bool {
m.lock.RLock()
defer m.lock.RUnlock()
for key, val := range m.data {
if fn(key, val) {
return true
}
}
return false
}
func (m *Map[Key, Val]) Get(key Key, fn func(Val)) bool {
@ -46,15 +78,45 @@ func (m *Map[Key, Val]) Get(key Key, fn func(Val)) bool {
}
func (m *Map[Key, Val]) GetErr(key Key, fn func(Val) error) (bool, error) {
m.lock.RLock()
defer m.lock.RUnlock()
var err error
ok := m.Get(key, func(val Val) {
err = fn(val)
})
return ok, err
}
func (m *Map[Key, Val]) GetDelete(key Key, fn func(Val)) bool {
m.lock.Lock()
defer m.lock.Unlock()
val, ok := m.data[key]
if !ok {
return false, nil
return false
}
return true, fn(val)
fn(val)
delete(m.data, key)
if idx := xslices.Index(m.order, key); idx >= 0 {
m.order = append(m.order[:idx], m.order[idx+1:]...)
} else {
panic("order and data out of sync")
}
return true
}
func (m *Map[Key, Val]) GetDeleteErr(key Key, fn func(Val) error) (bool, error) {
var err error
ok := m.GetDelete(key, func(val Val) {
err = fn(val)
})
return ok, err
}
func (m *Map[Key, Val]) Set(key Key, val Val) {
@ -62,84 +124,140 @@ func (m *Map[Key, Val]) Set(key Key, val Val) {
defer m.lock.Unlock()
m.data[key] = val
m.order = append(m.order, key)
if m.sort != nil {
slices.SortFunc(m.order, func(a, b Key) bool {
return m.sort(a, b, m.data)
})
}
}
func (m *Map[Key, Val]) Delete(key Key) {
func (m *Map[Key, Val]) SetFrom(key Key, other Key) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.data, key)
m.data[key] = m.data[other]
m.order = append(m.order, key)
if m.sort != nil {
slices.SortFunc(m.order, func(a, b Key) bool {
return m.sort(a, b, m.data)
})
}
}
func (m *Map[Key, Val]) Iter(fn func(key Key, val Val)) {
m.lock.RLock()
defer m.lock.RUnlock()
for key, val := range m.data {
fn(key, val)
for _, key := range m.order {
fn(key, m.data[key])
}
}
func (m *Map[Key, Val]) Keys(fn func(keys []Key)) {
m.lock.RLock()
defer m.lock.RUnlock()
func (m *Map[Key, Val]) IterKeys(fn func(Key)) {
m.Iter(func(key Key, _ Val) {
fn(key)
})
}
fn(maps.Keys(m.data))
func (m *Map[Key, Val]) IterKeysErr(fn func(Key) error) error {
var err error
m.IterKeys(func(key Key) {
if err != nil {
return
}
err = fn(key)
})
return err
}
func (m *Map[Key, Val]) IterValues(fn func(Val)) {
m.Iter(func(_ Key, val Val) {
fn(val)
})
}
func (m *Map[Key, Val]) IterValuesErr(fn func(Val) error) error {
var err error
m.IterValues(func(val Val) {
if err != nil {
return
}
err = fn(val)
})
return err
}
func (m *Map[Key, Val]) Values(fn func(vals []Val)) {
m.lock.RLock()
defer m.lock.RUnlock()
fn(maps.Values(m.data))
vals := make([]Val, len(m.order))
for i, key := range m.order {
vals[i] = m.data[key]
}
fn(vals)
}
func GetMap[Key comparable, Val, Ret any](m *Map[Key, Val], key Key, fn func(Val) Ret, fallback func() Ret) Ret {
func (m *Map[Key, Val]) ValuesErr(fn func(vals []Val) error) error {
var err error
m.Values(func(vals []Val) {
err = fn(vals)
})
return err
}
func (m *Map[Key, Val]) MapErr(fn func(map[Key]Val) error) error {
m.lock.RLock()
defer m.lock.RUnlock()
val, ok := m.data[key]
if !ok {
return fallback()
}
return fn(val)
return fn(m.data)
}
func GetMapErr[Key comparable, Val, Ret any](m *Map[Key, Val], key Key, fn func(Val) (Ret, error), fallback func() (Ret, error)) (Ret, error) {
m.lock.RLock()
defer m.lock.RUnlock()
func MapGetRet[Key comparable, Val, Ret any](m *Map[Key, Val], key Key, fn func(Val) Ret) (Ret, bool) {
var ret Ret
val, ok := m.data[key]
if !ok {
return fallback()
}
ok := m.Get(key, func(val Val) {
ret = fn(val)
})
return fn(val)
return ret, ok
}
func FindMap[Key comparable, Val, Ret any](m *Map[Key, Val], cmp func(Val) bool, fn func(Val) Ret, fallback func() Ret) Ret {
m.lock.RLock()
defer m.lock.RUnlock()
func MapValuesRet[Key comparable, Val, Ret any](m *Map[Key, Val], fn func([]Val) Ret) Ret {
var ret Ret
for _, val := range m.data {
if cmp(val) {
return fn(val)
}
}
m.Values(func(vals []Val) {
ret = fn(vals)
})
return fallback()
return ret
}
func FindMapErr[Key comparable, Val, Ret any](m *Map[Key, Val], cmp func(Val) bool, fn func(Val) (Ret, error), fallback func() (Ret, error)) (Ret, error) {
m.lock.RLock()
defer m.lock.RUnlock()
func MapValuesRetErr[Key comparable, Val, Ret any](m *Map[Key, Val], fn func([]Val) (Ret, error)) (Ret, error) {
var ret Ret
for _, val := range m.data {
if cmp(val) {
return fn(val)
}
}
err := m.ValuesErr(func(vals []Val) error {
var err error
return fallback()
ret, err = fn(vals)
return err
})
return ret, err
}

97
internal/safe/map_test.go Normal file
View File

@ -0,0 +1,97 @@
package safe
import "testing"
func TestSafe_Map(t *testing.T) {
m := NewMap(func(a, b string, data map[string]string) bool {
return a < b
})
m.Set("a", "b")
if !m.Has("a") {
t.Fatal("expected to have key")
}
if m.Has("b") {
t.Fatal("expected not to have key")
}
m.Set("b", "c")
if !m.Has("b") {
t.Fatal("expected to have key")
}
if !m.HasFunc(func(key string, val string) bool {
return key == "b"
}) {
t.Fatal("expected to have key")
}
if !m.Get("b", func(val string) {
if val != "c" {
t.Fatal("expected to have value")
}
}) {
t.Fatal("expected to have key")
}
if !m.Index(0, func(key string, val string) {
if key != "a" || val != "b" {
t.Fatal("expected to have key and value")
}
}) {
t.Fatal("expected to have index")
}
if !m.Index(1, func(key string, val string) {
if key != "b" || val != "c" {
t.Fatal("expected to have key and value")
}
}) {
t.Fatal("expected to have index")
}
if m.Index(2, func(key string, val string) {
t.Fatal("expected not to have index")
}) {
t.Fatal("expected not to have index")
}
if !m.GetDelete("b", func(val string) {
if val != "c" {
t.Fatal("expected to have value")
}
}) {
t.Fatal("expected to have key")
}
if m.Has("b") {
t.Fatal("expected not to have key")
}
if m.GetDelete("b", func(val string) {
t.Fatal("expected not to have value")
}) {
t.Fatal("expected not to have key")
}
if !m.Index(0, func(key string, val string) {
if key != "a" || val != "b" {
t.Fatal("expected to have key and value")
}
}) {
t.Fatal("expected to have index")
}
m.Values(func(val []string) {
if len(val) != 1 {
t.Fatal("expected to have values")
}
if val[0] != "b" {
t.Fatal("expected to have value")
}
})
}

View File

@ -1,46 +0,0 @@
package safe
import "golang.org/x/exp/maps"
type Set[Val comparable] Map[Val, struct{}]
func NewSet[Val comparable](vals ...Val) *Set[Val] {
set := (*Set[Val])(NewMap[Val, struct{}](nil))
for _, val := range vals {
set.Insert(val)
}
return set
}
func (m *Set[Val]) Has(key Val) bool {
m.lock.RLock()
defer m.lock.RUnlock()
_, ok := m.data[key]
return ok
}
func (m *Set[Val]) Insert(key Val) {
m.lock.Lock()
defer m.lock.Unlock()
m.data[key] = struct{}{}
}
func (m *Set[Val]) Iter(fn func(key Val)) {
m.lock.RLock()
defer m.lock.RUnlock()
for key := range m.data {
fn(key)
}
}
func (m *Set[Val]) Values(fn func(vals []Val)) {
m.lock.RLock()
defer m.lock.RUnlock()
fn(maps.Keys(m.data))
}

View File

@ -1,13 +1,17 @@
package safe
import "sync"
import (
"sync"
type Slice[Val any] struct {
"github.com/bradenaw/juniper/xslices"
)
type Slice[Val comparable] struct {
data []Val
lock sync.RWMutex
}
func NewSlice[Val any](from []Val) *Slice[Val] {
func NewSlice[Val comparable](from ...Val) *Slice[Val] {
s := &Slice[Val]{
data: make([]Val, len(from)),
}
@ -17,37 +21,27 @@ func NewSlice[Val any](from []Val) *Slice[Val] {
return s
}
func (s *Slice[Val]) Get(fn func(data []Val)) {
func (s *Slice[Val]) Iter(fn func(val Val)) {
s.lock.RLock()
defer s.lock.RUnlock()
fn(s.data)
for _, val := range s.data {
fn(val)
}
}
func (s *Slice[Val]) GetErr(fn func(data []Val) error) error {
s.lock.RLock()
defer s.lock.RUnlock()
return fn(s.data)
}
func (s *Slice[Val]) Set(data []Val) {
func (s *Slice[Val]) Append(val Val) {
s.lock.Lock()
defer s.lock.Unlock()
s.data = data
s.data = append(s.data, val)
}
func GetSlice[Val, Ret any](s *Slice[Val], fn func(data []Val) Ret) Ret {
s.lock.RLock()
defer s.lock.RUnlock()
func (s *Slice[Val]) Delete(val Val) {
s.lock.Lock()
defer s.lock.Unlock()
return fn(s.data)
}
func GetSliceErr[Val, Ret any](s *Slice[Val], fn func(data []Val) (Ret, error)) (Ret, error) {
s.lock.RLock()
defer s.lock.RUnlock()
return fn(s.data)
s.data = xslices.Filter(s.data, func(v Val) bool {
return v != val
})
}

View File

@ -0,0 +1,34 @@
package safe
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSlice(t *testing.T) {
s := NewSlice(1, 2, 3, 4, 5)
{
var have []int
s.Iter(func(val int) {
have = append(have, val)
})
require.Equal(t, []int{1, 2, 3, 4, 5}, have)
}
s.Append(6)
s.Delete(3)
{
var have []int
s.Iter(func(val int) {
have = append(have, val)
})
require.Equal(t, []int{1, 2, 4, 5, 6}, have)
}
}

View File

@ -13,37 +13,57 @@ func NewValue[T any](data T) *Value[T] {
}
}
func (s *Value[T]) Get(fn func(data T)) {
func (s *Value[T]) Load(fn func(data T)) {
s.lock.RLock()
defer s.lock.RUnlock()
fn(s.data)
}
func (s *Value[T]) GetErr(fn func(data T) error) error {
s.lock.RLock()
defer s.lock.RUnlock()
func (s *Value[T]) LoadErr(fn func(data T) error) error {
var err error
return fn(s.data)
s.Load(func(data T) {
err = fn(data)
})
return err
}
func (s *Value[T]) Set(data T) {
func (s *Value[T]) Save(data T) {
s.lock.Lock()
defer s.lock.Unlock()
s.data = data
}
func GetType[T, Ret any](s *Value[T], fn func(data T) Ret) Ret {
s.lock.RLock()
defer s.lock.RUnlock()
func (s *Value[T]) Mod(fn func(data *T)) {
s.lock.Lock()
defer s.lock.Unlock()
return fn(s.data)
fn(&s.data)
}
func GetTypeErr[T, Ret any](s *Value[T], fn func(data T) (Ret, error)) (Ret, error) {
s.lock.RLock()
defer s.lock.RUnlock()
func LoadRet[T, Ret any](s *Value[T], fn func(data T) Ret) Ret {
var ret Ret
return fn(s.data)
s.Load(func(data T) {
ret = fn(data)
})
return ret
}
func LoadRetErr[T, Ret any](s *Value[T], fn func(data T) (Ret, error)) (Ret, error) {
var ret Ret
err := s.LoadErr(func(data T) error {
var err error
ret, err = fn(data)
return err
})
return ret, err
}

View File

@ -0,0 +1,37 @@
package safe
import "testing"
func TestValue(t *testing.T) {
v := NewValue("foo")
v.Load(func(data string) {
if data != "foo" {
t.Error("expected foo")
}
})
v.Save("bar")
v.Load(func(data string) {
if data != "bar" {
t.Error("expected bar")
}
})
v.Mod(func(data *string) {
*data = "baz"
})
v.Load(func(data string) {
if data != "baz" {
t.Error("expected baz")
}
})
if LoadRet(v, func(data string) string {
return data
}) != "baz" {
t.Error("expected baz")
}
}