From e9d29a4813c18f5678e3ab9a5af1ee8acaf4e68e Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Thu, 5 Dec 2024 15:38:46 +0100 Subject: [PATCH 1/4] wip --- afterset.go | 49 ------------ afterset_test.go | 84 -------------------- bind.go | 93 +--------------------- bind_test.go | 5 ++ binder.go | 32 ++++++++ bindfunc_test.go | 196 ++++++++++++++++++++++++++++++++++++++++++++++ binding.go | 118 ++++++++++++++++++++++++++++ bindinghookget.go | 55 +++++++++++++ bindinghookset.go | 68 ++++++++++++++++ 9 files changed, 477 insertions(+), 223 deletions(-) delete mode 100644 afterset.go delete mode 100644 afterset_test.go create mode 100644 binder.go create mode 100644 bindfunc_test.go create mode 100644 binding.go create mode 100644 bindinghookget.go create mode 100644 bindinghookset.go diff --git a/afterset.go b/afterset.go deleted file mode 100644 index 427fed7..0000000 --- a/afterset.go +++ /dev/null @@ -1,49 +0,0 @@ -package jaws - -import "time" - -type AfterSetFunc[T comparable] func(bind Binding[T]) (err error) - -type AfterSetter[T comparable] struct { - Binding[T] - AfterSetFunc[T] -} - -func (as *AfterSetter[T]) Set(value T) (err error) { - if err = as.Binding.Set(value); err == nil { - err = as.AfterSetFunc(as.Binding) - } - return -} - -func (as *AfterSetter[T]) JawsSet(elem *Element, value T) (err error) { - return as.Set(value) -} - -func (as *AfterSetter[T]) JawsSetString(e *Element, val string) (err error) { - return as.JawsSet(e, any(val).(T)) -} - -func (as *AfterSetter[T]) JawsSetFloat(e *Element, val float64) (err error) { - return as.JawsSet(e, any(val).(T)) -} - -func (as *AfterSetter[T]) JawsSetBool(e *Element, val bool) (err error) { - return as.JawsSet(e, any(val).(T)) -} - -func (as *AfterSetter[T]) JawsSetTime(elem *Element, value time.Time) error { - return as.JawsSet(elem, any(value).(T)) -} - -// AfterSet returns a wrapped Binding with a function to call after a -// successful bind.Set. -// -// The functions return value replaces that of Set, and the function may -// use the Binding to inspect and modify the value. -func AfterSet[T comparable](bind Binding[T], fn AfterSetFunc[T]) *AfterSetter[T] { - return &AfterSetter[T]{ - Binding: bind, - AfterSetFunc: fn, - } -} diff --git a/afterset_test.go b/afterset_test.go deleted file mode 100644 index 4fd9135..0000000 --- a/afterset_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package jaws - -import ( - "sync" - "testing" - "time" -) - -func TestAfterSet_String(t *testing.T) { - var mu sync.Mutex - var value string - var called int - - as := AfterSet(Bind(&mu, &value), func(bind Binding[string]) (err error) { - called++ - return - }) - if err := as.JawsSetString(nil, "foo"); err != nil { - t.Error(err) - } - if called != 1 { - t.Error(called) - } - if tag := as.JawsGetTag(nil); tag != &value { - t.Error(tag) - } - if err := as.JawsSetString(nil, "foo"); err != ErrValueUnchanged { - t.Error(err) - } - if called != 1 { - t.Error(called) - } -} - -func TestAfterSet_Float(t *testing.T) { - var mu sync.Mutex - var value float64 - var called bool - - as := AfterSet(Bind(&mu, &value), func(bind Binding[float64]) (err error) { - called = true - return - }) - if err := as.JawsSetFloat(nil, 1); err != nil { - t.Error(err) - } - if !called { - t.Error(called) - } -} - -func TestAfterSet_Bool(t *testing.T) { - var mu sync.Mutex - var value bool - var called bool - - as := AfterSet(Bind(&mu, &value), func(bind Binding[bool]) (err error) { - called = true - return - }) - if err := as.JawsSetBool(nil, !value); err != nil { - t.Error(err) - } - if !called { - t.Error(called) - } -} - -func TestAfterSet_Time(t *testing.T) { - var mu sync.Mutex - var value time.Time - var called bool - - as := AfterSet(Bind(&mu, &value), func(bind Binding[time.Time]) (err error) { - called = true - return - }) - if err := as.JawsSetTime(nil, time.Now()); err != nil { - t.Error(err) - } - if !called { - t.Error(called) - } -} diff --git a/bind.go b/bind.go index 6260f65..f02a9b4 100644 --- a/bind.go +++ b/bind.go @@ -2,98 +2,11 @@ package jaws import ( "sync" - "time" ) -// Binding combines a lock with a pointer to a value of type T, and implements Setter[T]. -// It also implements BoolSetter, FloatSetter, StringSetter and TimeSetter, but will panic -// if the underlying type T is not correct. -type Binding[T comparable] struct { - L sync.Locker - P *T -} - -var ( - _ BoolSetter = Binding[bool]{} - _ FloatSetter = Binding[float64]{} - _ StringSetter = Binding[string]{} - _ TimeSetter = Binding[time.Time]{} -) - -func (bind Binding[T]) Get() (value T) { - if rl, ok := bind.L.(RLocker); ok { - rl.RLock() - value = *bind.P - rl.RUnlock() - } else { - bind.L.Lock() - value = *bind.P - bind.L.Unlock() - } - return -} - -func (bind Binding[T]) Set(value T) (err error) { - bind.L.Lock() - if value != *bind.P { - *bind.P = value - } else { - err = ErrValueUnchanged - } - bind.L.Unlock() - return -} - -func (bind Binding[T]) JawsGet(*Element) T { - return bind.Get() -} - -func (bind Binding[T]) JawsSet(elem *Element, value T) error { - return bind.Set(value) -} - -func (bind Binding[T]) JawsGetTag(*Request) any { - return bind.P -} - -func (bind Binding[T]) JawsSetString(e *Element, val string) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind Binding[T]) JawsGetString(e *Element) string { - return any(bind.JawsGet(e)).(string) -} - -func (bind Binding[T]) JawsSetFloat(e *Element, val float64) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind Binding[T]) JawsGetFloat(e *Element) float64 { - return any(bind.JawsGet(e)).(float64) -} - -func (bind Binding[T]) JawsSetBool(e *Element, val bool) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind Binding[T]) JawsGetBool(e *Element) bool { - return any(bind.JawsGet(e)).(bool) -} - -func (bind Binding[T]) JawsGetTime(elem *Element) time.Time { - return any(bind.JawsGet(elem)).(time.Time) -} - -func (bind Binding[T]) JawsSetTime(elem *Element, value time.Time) error { - return bind.JawsSet(elem, any(value).(T)) -} - -// Bind returns a Binding[T] with the given sync.Locker (or RWLocker) and a pointer to the underlying value of type T. -// -// It implements Setter[T]. It also implements BoolSetter, FloatSetter, StringSetter and TimeSetter, but will panic -// if the underlying type T is not correct. +// Bind returns a Binder[T] with the given sync.Locker (or RWLocker) and a pointer to the underlying value of type T. // // The pointer will be used as the UI tag. -func Bind[T comparable](l sync.Locker, p *T) Binding[T] { - return Binding[T]{L: l, P: p} +func Bind[T comparable](l sync.Locker, p *T) Binder[T] { + return Binding[T]{Locker: l, ptr: p} } diff --git a/bind_test.go b/bind_test.go index f76668d..bf6490e 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1,6 +1,7 @@ package jaws import ( + "reflect" "sync" "testing" "time" @@ -16,6 +17,10 @@ func Test_Bind_string(t *testing.T) { if s := v.JawsGetString(nil); s != "foo" { t.Error(s) } + + if tags := MustTagExpand(nil, v); !reflect.DeepEqual(tags, []any{&val}) { + t.Error(tags) + } } func Test_Bind_float64(t *testing.T) { diff --git a/binder.go b/binder.go new file mode 100644 index 0000000..11c4a31 --- /dev/null +++ b/binder.go @@ -0,0 +1,32 @@ +package jaws + +import "sync" + +type BindSetHook[T comparable] func(bind Binder[T], elem *Element, value T) (err error) +type BindGetHook[T comparable] func(bind Binder[T], elem *Element) (value T) +type BindSuccessHook func() + +type Binder[T comparable] interface { + sync.Locker + RLocker + Setter[T] + BoolSetter + StringSetter + FloatSetter + TimeSetter + JawsGetLocked(elem *Element) (value T) + JawsSetLocked(elem *Element, value T) (err error) + JawsGetTag(*Request) any + + // SetHook returns a Binder[T] that will call fn instead of JawsSetLocked + // The lock will be held at this point. + SetHook(fn BindSetHook[T]) (newbind Binder[T]) + + // GetHook returns a Binder[T] that will call fn instead of JawsGetLocked + // The lock (or RLock, if available) will be held at this point. + GetHook(fn BindGetHook[T]) (newbind Binder[T]) + + // Success returns a Binder[T] that will call fn after the value has been set + // with no errors. No locks are held at this point. + Success(fn BindSuccessHook) (newbind Binder[T]) +} diff --git a/bindfunc_test.go b/bindfunc_test.go new file mode 100644 index 0000000..84553c5 --- /dev/null +++ b/bindfunc_test.go @@ -0,0 +1,196 @@ +package jaws + +import ( + "reflect" + "sync" + "testing" + "time" +) + +func TestBindFunc_String(t *testing.T) { + var mu sync.Mutex + var val string + called1 := 0 + called2 := 0 + called3 := 0 + v := Bind(&mu, &val).SetHook(func(bind Binder[string], elem *Element, value string) (err error) { + called1++ + return bind.JawsSetLocked(elem, value) + }) + if err := v.JawsSetString(nil, "foo"); err != nil { + t.Error(err) + } + if s := v.JawsGetString(nil); s != "foo" { + t.Error(s) + } + if called1 != 1 { + t.Error(called1) + } + if tags := MustTagExpand(nil, v); !reflect.DeepEqual(tags, []any{&val}) { + t.Error(tags) + } + v2 := v.SetHook(func(bind Binder[string], elem *Element, value string) (err error) { + called2++ + return bind.JawsSetLocked(elem, value) + }).Success(func() { + called3++ + }) + if err := v2.JawsSetString(nil, "bar"); err != nil { + t.Error(err) + } + if s := v.JawsGetString(nil); s != "bar" { + t.Error(s) + } + v3 := v2.GetHook(func(bind Binder[string], elem *Element) (value string) { + return "quux" + }) + if s := v3.JawsGetString(nil); s != "quux" { + t.Error(s) + } + if called1 != 2 { + t.Error(called1) + } + if called2 != 1 { + t.Error(called2) + } + if called3 != 1 { + t.Error(called3) + } +} + +func TestBindFunc_Float(t *testing.T) { + var mu sync.Mutex + var val float64 + called := 0 + v := Bind(&mu, &val).SetHook(func(bind Binder[float64], elem *Element, value float64) (err error) { + called++ + return bind.JawsSetLocked(elem, value) + }) + if err := v.JawsSetFloat(nil, 123); err != nil { + t.Error(err) + } + if x := v.JawsGetFloat(nil); x != 123 { + t.Error(x) + } + called2 := 0 + called3 := 0 + v2 := v.SetHook(func(bind Binder[float64], elem *Element, value float64) (err error) { + called2++ + return bind.JawsSetLocked(elem, value) + }).Success(func() { + called3++ + }) + if err := v2.JawsSetFloat(nil, 345); err != nil { + t.Error(err) + } + if s := v.JawsGetFloat(nil); s != 345 { + t.Error(s) + } + v3 := v2.GetHook(func(bind Binder[float64], elem *Element) (value float64) { + return 234 + }) + if called2 != 2 { + t.Error(called) + } + if called3 != 1 { + t.Error(called) + } + if s := v3.JawsGetFloat(nil); s != 234 { + t.Error(s) + } + if called != 1 { + t.Error(called) + } +} + +func TestBindFunc_Bool(t *testing.T) { + var mu sync.Mutex + var val bool + called := 0 + v := Bind(&mu, &val).SetHook(func(bind Binder[bool], elem *Element, value bool) (err error) { + called++ + return bind.JawsSetLocked(elem, value) + }) + if err := v.JawsSetBool(nil, true); err != nil { + t.Error(err) + } + if x := v.JawsGetBool(nil); x != true { + t.Error(x) + } + called2 := 0 + called3 := 0 + v2 := v.SetHook(func(bind Binder[bool], elem *Element, value bool) (err error) { + called2++ + return bind.JawsSetLocked(elem, value) + }).Success(func() { + called3++ + }) + if err := v2.JawsSetBool(nil, false); err != nil { + t.Error(err) + } + if s := v.JawsGetBool(nil); s != false { + t.Error(s) + } + v3 := v2.GetHook(func(bind Binder[bool], elem *Element) (value bool) { + return true + }) + if called2 != 2 { + t.Error(called) + } + if called3 != 1 { + t.Error(called) + } + if s := v3.JawsGetBool(nil); !s { + t.Error(s) + } + if called != 1 { + t.Error(called) + } +} + +func TestBindFunc_Time(t *testing.T) { + var mu sync.Mutex + var val time.Time + called := 0 + v := Bind(&mu, &val).SetHook(func(bind Binder[time.Time], elem *Element, value time.Time) (err error) { + called++ + return bind.JawsSetLocked(elem, value) + }) + want := time.Now() + if err := v.JawsSetTime(nil, want); err != nil { + t.Error(err) + } + if x := v.JawsGetTime(nil); x != want { + t.Error(x) + } + called2 := 0 + called3 := 0 + v2 := v.SetHook(func(bind Binder[time.Time], elem *Element, value time.Time) (err error) { + called2++ + return bind.JawsSetLocked(elem, value) + }).Success(func() { + called3++ + }) + want = want.Add(time.Second) + if err := v2.JawsSetTime(nil, want); err != nil { + t.Error(err) + } + if s := v.JawsGetTime(nil); s != want { + t.Error(s) + } + v3 := v2.GetHook(func(bind Binder[time.Time], elem *Element) (value time.Time) { + return time.Time{} + }) + if called2 != 2 { + t.Error(called) + } + if called3 != 1 { + t.Error(called) + } + if s := v3.JawsGetTime(nil); !s.IsZero() { + t.Error(s) + } + if called != 1 { + t.Error(called) + } +} diff --git a/binding.go b/binding.go new file mode 100644 index 0000000..feeff46 --- /dev/null +++ b/binding.go @@ -0,0 +1,118 @@ +package jaws + +import ( + "sync" + "time" +) + +// Binding combines a lock with a pointer to a value of type T, and implements Setter[T]. +// It also implements BoolSetter, FloatSetter, StringSetter and TimeSetter, but will panic +// if the underlying type T is not correct. +type Binding[T comparable] struct { + sync.Locker + ptr *T +} + +var ( + _ BoolSetter = Binding[bool]{} + _ FloatSetter = Binding[float64]{} + _ StringSetter = Binding[string]{} + _ TimeSetter = Binding[time.Time]{} +) + +func (bind Binding[T]) JawsGetLocked(*Element) T { + return *bind.ptr +} + +func (bind Binding[T]) JawsSetLocked(elem *Element, value T) (err error) { + if value == *bind.ptr { + return ErrValueUnchanged + } + *bind.ptr = value + return nil +} + +func (bind Binding[T]) JawsGet(elem *Element) T { + bind.RLock() + defer bind.RUnlock() + return bind.JawsGetLocked(elem) +} + +func (bind Binding[T]) JawsSet(elem *Element, value T) error { + bind.Lock() + defer bind.Unlock() + return bind.JawsSetLocked(elem, value) +} + +func (bind Binding[T]) JawsGetTag(*Request) any { + return bind.ptr +} + +func (bind Binding[T]) JawsSetString(e *Element, val string) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind Binding[T]) JawsGetString(e *Element) string { + return any(bind.JawsGet(e)).(string) +} + +func (bind Binding[T]) JawsSetFloat(e *Element, val float64) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind Binding[T]) JawsGetFloat(e *Element) float64 { + return any(bind.JawsGet(e)).(float64) +} + +func (bind Binding[T]) JawsSetBool(e *Element, val bool) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind Binding[T]) JawsGetBool(e *Element) bool { + return any(bind.JawsGet(e)).(bool) +} + +func (bind Binding[T]) JawsGetTime(elem *Element) time.Time { + return any(bind.JawsGet(elem)).(time.Time) +} + +func (bind Binding[T]) JawsSetTime(elem *Element, value time.Time) error { + return bind.JawsSet(elem, any(value).(T)) +} + +func (bind Binding[T]) RLock() { + if rl, ok := bind.Locker.(RLocker); ok { + rl.RLock() + } else { + bind.Lock() + } +} + +func (bind Binding[T]) RUnlock() { + if rl, ok := bind.Locker.(RLocker); ok { + rl.RUnlock() + } else { + bind.Unlock() + } +} + +func (bind Binding[T]) SetHook(setFn BindSetHook[T]) Binder[T] { + return &BindingHookSet[T]{ + Binder: bind, + BindSetHook: setFn, + } +} + +func (bind Binding[T]) GetHook(setFn BindGetHook[T]) Binder[T] { + return &BindingHookGet[T]{ + Binder: bind, + BindGetHook: setFn, + } +} + +func (bind Binding[T]) Success(setFn BindSuccessHook) Binder[T] { + return &BindingHookSet[T]{ + Binder: bind, + BindSuccessHook: setFn, + } +} diff --git a/bindinghookget.go b/bindinghookget.go new file mode 100644 index 0000000..b2a3fcc --- /dev/null +++ b/bindinghookget.go @@ -0,0 +1,55 @@ +package jaws + +import "time" + +type BindingHookGet[T comparable] struct { + Binder[T] + BindGetHook[T] +} + +func (bind *BindingHookGet[T]) JawsGetLocked(elem *Element) T { + return bind.BindGetHook(bind.Binder, elem) +} + +func (bind *BindingHookGet[T]) JawsGet(elem *Element) T { + bind.RLock() + defer bind.RUnlock() + return bind.JawsGetLocked(elem) +} + +func (bind *BindingHookGet[T]) JawsGetString(elem *Element) string { + return any(bind.JawsGet(elem)).(string) +} + +func (bind *BindingHookGet[T]) JawsGetFloat(elem *Element) float64 { + return any(bind.JawsGet(elem)).(float64) +} + +func (bind *BindingHookGet[T]) JawsGetBool(elem *Element) bool { + return any(bind.JawsGet(elem)).(bool) +} + +func (bind *BindingHookGet[T]) JawsGetTime(elem *Element) time.Time { + return any(bind.JawsGet(elem)).(time.Time) +} + +func (bind *BindingHookGet[T]) SetHook(setFn BindSetHook[T]) Binder[T] { + return &BindingHookSet[T]{ + Binder: bind, + BindSetHook: setFn, + } +} + +func (bind *BindingHookGet[T]) GetHook(setFn BindGetHook[T]) Binder[T] { + return &BindingHookGet[T]{ + Binder: bind, + BindGetHook: setFn, + } +} + +func (bind *BindingHookGet[T]) Success(setFn BindSuccessHook) Binder[T] { + return &BindingHookSet[T]{ + Binder: bind, + BindSuccessHook: setFn, + } +} diff --git a/bindinghookset.go b/bindinghookset.go new file mode 100644 index 0000000..d4e3f87 --- /dev/null +++ b/bindinghookset.go @@ -0,0 +1,68 @@ +package jaws + +import "time" + +type BindingHookSet[T comparable] struct { + Binder[T] + BindSetHook[T] + BindSuccessHook +} + +func (bind *BindingHookSet[T]) JawsSetLocked(elem *Element, value T) error { + if bind.BindSetHook != nil { + return bind.BindSetHook(bind.Binder, elem, value) + } + return bind.Binder.JawsSetLocked(elem, value) +} + +func (bind *BindingHookSet[T]) jawsSetLocking(elem *Element, value T) (err error) { + bind.Lock() + defer bind.Unlock() + return bind.JawsSetLocked(elem, value) +} + +func (bind *BindingHookSet[T]) JawsSet(elem *Element, value T) (err error) { + if err = bind.jawsSetLocking(elem, value); err == nil { + if bind.BindSuccessHook != nil { + bind.BindSuccessHook() + } + } + return +} + +func (bind *BindingHookSet[T]) JawsSetString(e *Element, val string) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind *BindingHookSet[T]) JawsSetFloat(e *Element, val float64) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind *BindingHookSet[T]) JawsSetBool(e *Element, val bool) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind *BindingHookSet[T]) JawsSetTime(elem *Element, value time.Time) error { + return bind.JawsSet(elem, any(value).(T)) +} + +func (bind *BindingHookSet[T]) SetHook(setFn BindSetHook[T]) Binder[T] { + return &BindingHookSet[T]{ + Binder: bind, + BindSetHook: setFn, + } +} + +func (bind *BindingHookSet[T]) GetHook(setFn BindGetHook[T]) Binder[T] { + return &BindingHookGet[T]{ + Binder: bind, + BindGetHook: setFn, + } +} + +func (bind *BindingHookSet[T]) Success(setFn BindSuccessHook) Binder[T] { + return &BindingHookSet[T]{ + Binder: bind, + BindSuccessHook: setFn, + } +} From 2593a727439625a9e2fb00270e90af21c39a87a1 Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Fri, 6 Dec 2024 11:02:03 +0100 Subject: [PATCH 2/4] wip --- bind_test.go | 328 +++++++++++++++++++++++++++++++++++++++++----- binder.go | 58 ++++++-- bindfunc_test.go | 196 --------------------------- binding.go | 71 ++++++++-- bindinghook.go | 140 ++++++++++++++++++++ bindinghookget.go | 55 -------- bindinghookset.go | 68 ---------- 7 files changed, 536 insertions(+), 380 deletions(-) delete mode 100644 bindfunc_test.go create mode 100644 bindinghook.go delete mode 100644 bindinghookget.go delete mode 100644 bindinghookset.go diff --git a/bind_test.go b/bind_test.go index bf6490e..d206db2 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1,75 +1,333 @@ package jaws import ( + "io" "reflect" "sync" "testing" "time" ) -func Test_Bind_string(t *testing.T) { +func TestBind_Hook_Success_panic(t *testing.T) { + defer func() { + if x := recover(); x == nil { + t.Fail() + } + }() + var mu sync.Mutex + var val string + Bind(&mu, &val).Success(func(n int) {}) + t.Fail() +} + +func TestBind_Hook_Success_breaksonerr(t *testing.T) { var mu sync.Mutex var val string - v := Bind(&mu, &val) - if err := v.JawsSetString(nil, "foo"); err != nil { + + calls1 := 0 + calls2 := 0 + calls3 := 0 + bind1 := Bind(&mu, &val). + Success(func() { + calls1++ + }). + Success(func() error { + calls2++ + return io.EOF + }). + Success(func() { + calls3++ + }) + if err := bind1.JawsSet(nil, "foo"); err != io.EOF { t.Error(err) } - if s := v.JawsGetString(nil); s != "foo" { - t.Error(s) + if calls1 != 1 { + t.Error(calls1) + } + if calls2 != 1 { + t.Error(calls2) + } + if calls3 != 0 { + t.Error(calls3) } +} + +func testBind_Hook_Success[T comparable](t *testing.T, testval T) { + var mu sync.Mutex + var val T + var blankval T - if tags := MustTagExpand(nil, v); !reflect.DeepEqual(tags, []any{&val}) { - t.Error(tags) + calls1 := 0 + bind1 := Bind(&mu, &val). + Success(func() { + calls1++ + }) + if err := bind1.JawsSet(nil, testval); err != nil { + t.Error(err) + } + if x := bind1.JawsGet(nil); x != testval { + t.Error(x) + } + if err := bind1.JawsSet(nil, testval); err != ErrValueUnchanged { + t.Error(err) + } + if calls1 != 1 { + t.Error(calls1) + } + tags1 := MustTagExpand(nil, bind1) + if !reflect.DeepEqual(tags1, []any{&val}) { + t.Error(tags1) + } + + calls2 := 0 + bind2 := bind1. + Success(func() (err error) { + calls2++ + if calls1 <= calls2 { + t.Error(calls1, calls2) + } + return + }) + if err := bind2.JawsSet(nil, blankval); err != nil { + t.Error(err) + } + if calls1 != 2 { + t.Error(calls1) + } + if calls2 != 1 { + t.Error(calls2) + } + tags2 := MustTagExpand(nil, bind2) + if !reflect.DeepEqual(tags2, []any{&val}) { + t.Error(tags2) + } + + calls3 := 0 + bind3 := bind2. + Success(func(*Element) { + calls3++ + if calls2 <= calls3 { + t.Error(calls2, calls3) + } + }) + if err := bind3.JawsSet(nil, testval); err != nil { + t.Error(err) + } + if calls1 != 3 { + t.Error(calls1) + } + if calls2 != 2 { + t.Error(calls2) + } + if calls3 != 1 { + t.Error(calls3) + } + + calls4 := 0 + bind4 := bind3. + Success(func(*Element) (err error) { + calls4++ + if calls3 <= calls4 { + t.Error(calls3, calls4) + } + return + }) + if err := bind4.JawsSet(nil, blankval); err != nil { + t.Error(err) + } + if calls1 != 4 { + t.Error(calls1) + } + if calls2 != 3 { + t.Error(calls2) + } + if calls3 != 2 { + t.Error(calls3) + } + if calls4 != 1 { + t.Error(calls4) } } -func Test_Bind_float64(t *testing.T) { +func testBind_Hook_Set[T comparable](t *testing.T, testval T) { var mu sync.Mutex - var val float64 - v := Bind(&mu, &val) - if err := v.JawsSetFloat(nil, 1.2); err != nil { + var val T + var blankval T + + calls1 := 0 + bind1 := Bind(&mu, &val). + SetLocked(func(bind Binder[T], elem *Element, value T) (err error) { + calls1++ + return bind.JawsSetLocked(elem, value) + }) + if err := bind1.JawsSet(nil, testval); err != nil { + t.Error(err) + } + if x := bind1.JawsGet(nil); x != testval { + t.Error(x) + } + if err := bind1.JawsSet(nil, testval); err != ErrValueUnchanged { + t.Error(err) + } + if calls1 != 2 { + t.Error(calls1) + } + tags1 := MustTagExpand(nil, bind1) + if !reflect.DeepEqual(tags1, []any{&val}) { + t.Error(tags1) + } + + calls2 := 0 + bind2 := bind1. + SetLocked(func(bind Binder[T], elem *Element, value T) (err error) { + calls2++ + return bind.JawsSetLocked(elem, value) + }) + if err := bind2.JawsSet(nil, blankval); err != nil { + t.Error(err) + } + if calls1 != 3 { + t.Error(calls1) + } + if calls2 != 1 { + t.Error(calls2) + } + tags2 := MustTagExpand(nil, bind2) + if !reflect.DeepEqual(tags2, []any{&val}) { + t.Error(tags2) + } +} + +func testBind_Hook_Get[T comparable](t *testing.T, testval T) { + var mu sync.Mutex + var val T + var blankval T + + calls1 := 0 + bind1 := Bind(&mu, &val). + GetLocked(func(bind Binder[T], elem *Element) (value T) { + calls1++ + return bind.JawsGetLocked(elem) + }) + if err := bind1.JawsSet(nil, testval); err != nil { + t.Error(err) + } + if x := bind1.JawsGet(nil); x != testval { + t.Error(x) + } + if err := bind1.JawsSet(nil, testval); err != ErrValueUnchanged { t.Error(err) } - if s := v.JawsGetFloat(nil); s != 1.2 { - t.Error(s) + if calls1 != 1 { + t.Error(calls1) + } + tags1 := MustTagExpand(nil, bind1) + if !reflect.DeepEqual(tags1, []any{&val}) { + t.Error(tags1) + } + + calls2 := 0 + bind2 := bind1. + GetLocked(func(bind Binder[T], elem *Element) (value T) { + calls2++ + return bind.JawsGetLocked(elem) + }) + if err := bind2.JawsSet(nil, blankval); err != nil { + t.Error(err) + } + if x := bind2.JawsGet(nil); x != blankval { + t.Error(x) + } + if calls1 != 2 { + t.Error(calls1) + } + if calls2 != 1 { + t.Error(calls2) + } + tags2 := MustTagExpand(nil, bind2) + if !reflect.DeepEqual(tags2, []any{&val}) { + t.Error(tags2) } } -func Test_Bind_bool(t *testing.T) { +func testBind_Hooks[T comparable](t *testing.T, testval T) { + testBind_Hook_Success(t, testval) + testBind_Hook_Set(t, testval) + testBind_Hook_Get(t, testval) +} + +func testBind_StringSetter(t *testing.T, v StringSetter) { + val := v.JawsGetString(nil) + "!" + if err := v.JawsSetString(nil, val); err != nil { + t.Error(err) + } + if x := v.JawsGetString(nil); x != val { + t.Error(x) + } +} + +func TestBindFunc_String(t *testing.T) { var mu sync.Mutex - var val bool - v := Bind(&mu, &val) - if err := v.JawsSetBool(nil, true); err != nil { + var val string + + testBind_Hooks(t, "foo") + testBind_StringSetter(t, Bind(&mu, &val).(StringSetter)) + testBind_StringSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(StringSetter)) +} + +func testBind_FloatSetter(t *testing.T, v FloatSetter) { + val := v.JawsGetFloat(nil) + 1 + if err := v.JawsSetFloat(nil, val); err != nil { t.Error(err) } - if s := v.JawsGetBool(nil); s != true { - t.Error(s) + if x := v.JawsGetFloat(nil); x != val { + t.Error(x) } } -func Test_Bind_Time(t *testing.T) { +func TestBindFunc_Float(t *testing.T) { var mu sync.Mutex - var val time.Time - v := Bind(&mu, &val) - now := time.Now() + var val float64 + + testBind_Hooks(t, float64(1.23)) + testBind_FloatSetter(t, Bind(&mu, &val).(FloatSetter)) + testBind_FloatSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(FloatSetter)) +} - if err := v.JawsSetTime(nil, now); err != nil { +func testBind_BoolSetter(t *testing.T, v BoolSetter) { + val := !v.JawsGetBool(nil) + if err := v.JawsSetBool(nil, val); err != nil { t.Error(err) } - if v := v.JawsGetTime(nil); v != now { - t.Error(v) + if x := v.JawsGetBool(nil); x != val { + t.Error(x) } } -func Test_Bind_panic(t *testing.T) { +func TestBindFunc_Bool(t *testing.T) { var mu sync.Mutex var val bool - defer func() { - if e := recover(); e == nil { - t.Error("expected panic") - } - }() - v := Bind(&mu, &val) - _ = v.JawsSetString(nil, "foo") - t.Error("expected panic") + + testBind_Hooks(t, true) + testBind_BoolSetter(t, Bind(&mu, &val).(BoolSetter)) + testBind_BoolSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(BoolSetter)) +} + +func testBind_TimeSetter(t *testing.T, v TimeSetter) { + val := v.JawsGetTime(nil).Add(time.Second) + if err := v.JawsSetTime(nil, val); err != nil { + t.Error(err) + } + if x := v.JawsGetTime(nil); x != val { + t.Error(x) + } +} + +func TestBindFunc_Time(t *testing.T) { + var mu sync.Mutex + var val time.Time + + testBind_Hooks(t, time.Now()) + testBind_TimeSetter(t, Bind(&mu, &val).(TimeSetter)) + testBind_TimeSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(TimeSetter)) } diff --git a/binder.go b/binder.go index 11c4a31..8ba8eb0 100644 --- a/binder.go +++ b/binder.go @@ -2,31 +2,63 @@ package jaws import "sync" +// BindSetHook is a function that replaces JawsSetLocked for a Binder. +// +// The lock will be held before calling the function, preferring RLock over Lock, if available. +// Do not lock or unlock the Binder in the function. Do not call JawsSet. +// +// The bind argument is the previous Binder in the chain, and you probably +// want to call it's JawsSetLocked first. type BindSetHook[T comparable] func(bind Binder[T], elem *Element, value T) (err error) + +// BindGetHook is a function that replaces JawsGetLocked for a Binder. +// +// The lock will be held before calling the function, preferring RLock over Lock, if available. +// Do not lock or unlock the Binder in the function. Do not call JawsGet. +// +// The bind argument is the previous Binder in the chain, and you probably +// want to call it's JawsGetLocked first. type BindGetHook[T comparable] func(bind Binder[T], elem *Element) (value T) -type BindSuccessHook func() + +// BindSuccessHook is a function to call when a call to JawsSet returns with no error. +// +// The Binder locks are not held when the function is called. +// +// Success hooks in a Binder chain are called in the order they were registered. +// If one of them returns an error, that error is returned from JawsSet and +// no more success hooks are called. +type BindSuccessHook func(*Element) (err error) type Binder[T comparable] interface { sync.Locker RLocker Setter[T] - BoolSetter - StringSetter - FloatSetter - TimeSetter + JawsGetTag(*Request) any + + JawsBinderPrev() Binder[T] // returns the previous Binder in the chail, or nil JawsGetLocked(elem *Element) (value T) JawsSetLocked(elem *Element, value T) (err error) - JawsGetTag(*Request) any - // SetHook returns a Binder[T] that will call fn instead of JawsSetLocked + // SetLocked returns a Binder[T] that will call fn instead of JawsSetLocked. + // // The lock will be held at this point. - SetHook(fn BindSetHook[T]) (newbind Binder[T]) + // Do not lock or unlock the Binder within fn. Do not call JawsSet. + SetLocked(fn BindSetHook[T]) (newbind Binder[T]) - // GetHook returns a Binder[T] that will call fn instead of JawsGetLocked - // The lock (or RLock, if available) will be held at this point. - GetHook(fn BindGetHook[T]) (newbind Binder[T]) + // GetLocked returns a Binder[T] that will call fn instead of JawsGetLocked. + // + // The lock will be held at this point, preferring RLock over Lock, if available. + // Do not lock or unlock the Binder within fn. Do not call JawsGet. + GetLocked(fn BindGetHook[T]) (newbind Binder[T]) // Success returns a Binder[T] that will call fn after the value has been set - // with no errors. No locks are held at this point. - Success(fn BindSuccessHook) (newbind Binder[T]) + // with no errors. No locks are held when the function is called. + // If the function returns an error, that will be returned from JawsSet. + // + // The function must have one of the following signatures: + // * func() + // * func() error + // * func(*Element) + // * func(*Element) error + Success(fn any) (newbind Binder[T]) } diff --git a/bindfunc_test.go b/bindfunc_test.go deleted file mode 100644 index 84553c5..0000000 --- a/bindfunc_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package jaws - -import ( - "reflect" - "sync" - "testing" - "time" -) - -func TestBindFunc_String(t *testing.T) { - var mu sync.Mutex - var val string - called1 := 0 - called2 := 0 - called3 := 0 - v := Bind(&mu, &val).SetHook(func(bind Binder[string], elem *Element, value string) (err error) { - called1++ - return bind.JawsSetLocked(elem, value) - }) - if err := v.JawsSetString(nil, "foo"); err != nil { - t.Error(err) - } - if s := v.JawsGetString(nil); s != "foo" { - t.Error(s) - } - if called1 != 1 { - t.Error(called1) - } - if tags := MustTagExpand(nil, v); !reflect.DeepEqual(tags, []any{&val}) { - t.Error(tags) - } - v2 := v.SetHook(func(bind Binder[string], elem *Element, value string) (err error) { - called2++ - return bind.JawsSetLocked(elem, value) - }).Success(func() { - called3++ - }) - if err := v2.JawsSetString(nil, "bar"); err != nil { - t.Error(err) - } - if s := v.JawsGetString(nil); s != "bar" { - t.Error(s) - } - v3 := v2.GetHook(func(bind Binder[string], elem *Element) (value string) { - return "quux" - }) - if s := v3.JawsGetString(nil); s != "quux" { - t.Error(s) - } - if called1 != 2 { - t.Error(called1) - } - if called2 != 1 { - t.Error(called2) - } - if called3 != 1 { - t.Error(called3) - } -} - -func TestBindFunc_Float(t *testing.T) { - var mu sync.Mutex - var val float64 - called := 0 - v := Bind(&mu, &val).SetHook(func(bind Binder[float64], elem *Element, value float64) (err error) { - called++ - return bind.JawsSetLocked(elem, value) - }) - if err := v.JawsSetFloat(nil, 123); err != nil { - t.Error(err) - } - if x := v.JawsGetFloat(nil); x != 123 { - t.Error(x) - } - called2 := 0 - called3 := 0 - v2 := v.SetHook(func(bind Binder[float64], elem *Element, value float64) (err error) { - called2++ - return bind.JawsSetLocked(elem, value) - }).Success(func() { - called3++ - }) - if err := v2.JawsSetFloat(nil, 345); err != nil { - t.Error(err) - } - if s := v.JawsGetFloat(nil); s != 345 { - t.Error(s) - } - v3 := v2.GetHook(func(bind Binder[float64], elem *Element) (value float64) { - return 234 - }) - if called2 != 2 { - t.Error(called) - } - if called3 != 1 { - t.Error(called) - } - if s := v3.JawsGetFloat(nil); s != 234 { - t.Error(s) - } - if called != 1 { - t.Error(called) - } -} - -func TestBindFunc_Bool(t *testing.T) { - var mu sync.Mutex - var val bool - called := 0 - v := Bind(&mu, &val).SetHook(func(bind Binder[bool], elem *Element, value bool) (err error) { - called++ - return bind.JawsSetLocked(elem, value) - }) - if err := v.JawsSetBool(nil, true); err != nil { - t.Error(err) - } - if x := v.JawsGetBool(nil); x != true { - t.Error(x) - } - called2 := 0 - called3 := 0 - v2 := v.SetHook(func(bind Binder[bool], elem *Element, value bool) (err error) { - called2++ - return bind.JawsSetLocked(elem, value) - }).Success(func() { - called3++ - }) - if err := v2.JawsSetBool(nil, false); err != nil { - t.Error(err) - } - if s := v.JawsGetBool(nil); s != false { - t.Error(s) - } - v3 := v2.GetHook(func(bind Binder[bool], elem *Element) (value bool) { - return true - }) - if called2 != 2 { - t.Error(called) - } - if called3 != 1 { - t.Error(called) - } - if s := v3.JawsGetBool(nil); !s { - t.Error(s) - } - if called != 1 { - t.Error(called) - } -} - -func TestBindFunc_Time(t *testing.T) { - var mu sync.Mutex - var val time.Time - called := 0 - v := Bind(&mu, &val).SetHook(func(bind Binder[time.Time], elem *Element, value time.Time) (err error) { - called++ - return bind.JawsSetLocked(elem, value) - }) - want := time.Now() - if err := v.JawsSetTime(nil, want); err != nil { - t.Error(err) - } - if x := v.JawsGetTime(nil); x != want { - t.Error(x) - } - called2 := 0 - called3 := 0 - v2 := v.SetHook(func(bind Binder[time.Time], elem *Element, value time.Time) (err error) { - called2++ - return bind.JawsSetLocked(elem, value) - }).Success(func() { - called3++ - }) - want = want.Add(time.Second) - if err := v2.JawsSetTime(nil, want); err != nil { - t.Error(err) - } - if s := v.JawsGetTime(nil); s != want { - t.Error(s) - } - v3 := v2.GetHook(func(bind Binder[time.Time], elem *Element) (value time.Time) { - return time.Time{} - }) - if called2 != 2 { - t.Error(called) - } - if called3 != 1 { - t.Error(called) - } - if s := v3.JawsGetTime(nil); !s.IsZero() { - t.Error(s) - } - if called != 1 { - t.Error(called) - } -} diff --git a/binding.go b/binding.go index feeff46..aee24c2 100644 --- a/binding.go +++ b/binding.go @@ -20,6 +20,10 @@ var ( _ TimeSetter = Binding[time.Time]{} ) +func (bind Binding[T]) JawsBinderPrev() Binder[T] { + return nil +} + func (bind Binding[T]) JawsGetLocked(*Element) T { return *bind.ptr } @@ -32,16 +36,18 @@ func (bind Binding[T]) JawsSetLocked(elem *Element, value T) (err error) { return nil } -func (bind Binding[T]) JawsGet(elem *Element) T { +func (bind Binding[T]) JawsGet(elem *Element) (value T) { bind.RLock() - defer bind.RUnlock() - return bind.JawsGetLocked(elem) + value = bind.JawsGetLocked(elem) + bind.RUnlock() + return } -func (bind Binding[T]) JawsSet(elem *Element, value T) error { +func (bind Binding[T]) JawsSet(elem *Element, value T) (err error) { bind.Lock() - defer bind.Unlock() - return bind.JawsSetLocked(elem, value) + err = bind.JawsSetLocked(elem, value) + bind.Unlock() + return } func (bind Binding[T]) JawsGetTag(*Request) any { @@ -96,23 +102,62 @@ func (bind Binding[T]) RUnlock() { } } -func (bind Binding[T]) SetHook(setFn BindSetHook[T]) Binder[T] { - return &BindingHookSet[T]{ +// SetLocked returns a Binder[T] that will call fn instead of JawsSetLocked. +// +// The lock will be held at this point. +// Do not lock or unlock the Binder within fn. Do not call JawsSet. +func (bind Binding[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { + return &BindingHook[T]{ Binder: bind, BindSetHook: setFn, } } -func (bind Binding[T]) GetHook(setFn BindGetHook[T]) Binder[T] { - return &BindingHookGet[T]{ +// GetLocked returns a Binder[T] that will call fn instead of JawsGetLocked. +// +// The lock will be held at this point, preferring RLock over Lock, if available. +// Do not lock or unlock the Binder within fn. Do not call JawsGet. +func (bind Binding[T]) GetLocked(setFn BindGetHook[T]) Binder[T] { + return &BindingHook[T]{ Binder: bind, BindGetHook: setFn, } } -func (bind Binding[T]) Success(setFn BindSuccessHook) Binder[T] { - return &BindingHookSet[T]{ +// Success returns a Binder[T] that will call fn after the value has been set +// with no errors. No locks are held when the function is called. +// If the function returns an error, that will be returned from JawsSet. +// +// The function must have one of the following signatures: +// - func() +// - func() error +// - func(*Element) +// - func(*Element) error +func (bind Binding[T]) Success(fn any) Binder[T] { + return &BindingHook[T]{ Binder: bind, - BindSuccessHook: setFn, + BindSuccessHook: wrapSuccessHook(fn), + } +} + +func wrapSuccessHook(fn any) (hook BindSuccessHook) { + switch fn := fn.(type) { + case func(): + return func(*Element) error { + fn() + return nil + } + case func() error: + return func(*Element) error { + return fn() + } + case func(*Element): + return func(elem *Element) error { + fn(elem) + return nil + } + case func(*Element) error: + return fn } + panic("Binding[T].Success(): function has wrong signature") } diff --git a/bindinghook.go b/bindinghook.go new file mode 100644 index 0000000..c9eb854 --- /dev/null +++ b/bindinghook.go @@ -0,0 +1,140 @@ +package jaws + +import "time" + +type successHooker interface { + JawsBinderSuccess(elem *Element) (err error) +} + +type BindingHook[T comparable] struct { + Binder[T] + BindGetHook[T] + BindSetHook[T] + BindSuccessHook +} + +func (bind *BindingHook[T]) JawsBinderPrev() Binder[T] { + return bind.Binder +} + +func (bind *BindingHook[T]) JawsGetLocked(elem *Element) T { + if bind.BindGetHook != nil { + return bind.BindGetHook(bind.Binder, elem) + } + return bind.Binder.JawsGetLocked(elem) +} + +func (bind *BindingHook[T]) JawsGet(elem *Element) T { + bind.RLock() + defer bind.RUnlock() + return bind.JawsGetLocked(elem) +} + +func (bind *BindingHook[T]) JawsSetLocked(elem *Element, value T) error { + if bind.BindSetHook != nil { + return bind.BindSetHook(bind.Binder, elem, value) + } + return bind.Binder.JawsSetLocked(elem, value) +} + +func (bind *BindingHook[T]) jawsSetLocking(elem *Element, value T) (err error) { + bind.Lock() + defer bind.Unlock() + return bind.JawsSetLocked(elem, value) +} + +func callSuccess[T comparable](binder Binder[T], elem *Element) (err error) { + if prev := binder.JawsBinderPrev(); prev != nil { + err = callSuccess(prev, elem) + } + if err == nil { + if successer, ok := binder.(successHooker); ok { + err = successer.JawsBinderSuccess(elem) + } + } + return +} + +func (bind *BindingHook[T]) JawsSet(elem *Element, value T) (err error) { + if err = bind.jawsSetLocking(elem, value); err == nil { + err = callSuccess(bind, elem) + } + return +} + +func (bind *BindingHook[T]) JawsGetString(elem *Element) string { + return any(bind.JawsGet(elem)).(string) +} + +func (bind *BindingHook[T]) JawsGetFloat(elem *Element) float64 { + return any(bind.JawsGet(elem)).(float64) +} + +func (bind *BindingHook[T]) JawsGetBool(elem *Element) bool { + return any(bind.JawsGet(elem)).(bool) +} + +func (bind *BindingHook[T]) JawsGetTime(elem *Element) time.Time { + return any(bind.JawsGet(elem)).(time.Time) +} + +func (bind *BindingHook[T]) JawsSetString(e *Element, val string) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind *BindingHook[T]) JawsSetFloat(e *Element, val float64) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind *BindingHook[T]) JawsSetBool(e *Element, val bool) (err error) { + return bind.JawsSet(e, any(val).(T)) +} + +func (bind *BindingHook[T]) JawsSetTime(elem *Element, value time.Time) error { + return bind.JawsSet(elem, any(value).(T)) +} + +// SetLocked returns a Binder[T] that will call fn instead of JawsSetLocked. +// +// The lock will be held at this point. +// Do not lock or unlock the Binder within fn. Do not call JawsSet. +func (bind *BindingHook[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { + return &BindingHook[T]{ + Binder: bind, + BindSetHook: setFn, + } +} + +// GetLocked returns a Binder[T] that will call fn instead of JawsGetLocked. +// +// The lock will be held at this point, preferring RLock over Lock, if available. +// Do not lock or unlock the Binder within fn. Do not call JawsGet. +func (bind *BindingHook[T]) GetLocked(setFn BindGetHook[T]) Binder[T] { + return &BindingHook[T]{ + Binder: bind, + BindGetHook: setFn, + } +} + +// Success returns a Binder[T] that will call fn after the value has been set +// with no errors. No locks are held when the function is called. +// If the function returns an error, that will be returned from JawsSet. +// +// The function must have one of the following signatures: +// - func() +// - func() error +// - func(*Element) +// - func(*Element) error +func (bind *BindingHook[T]) Success(fn any) Binder[T] { + return &BindingHook[T]{ + Binder: bind, + BindSuccessHook: wrapSuccessHook(fn), + } +} + +func (bind *BindingHook[T]) JawsBinderSuccess(elem *Element) (err error) { + if bind.BindSuccessHook != nil { + err = bind.BindSuccessHook(elem) + } + return +} diff --git a/bindinghookget.go b/bindinghookget.go deleted file mode 100644 index b2a3fcc..0000000 --- a/bindinghookget.go +++ /dev/null @@ -1,55 +0,0 @@ -package jaws - -import "time" - -type BindingHookGet[T comparable] struct { - Binder[T] - BindGetHook[T] -} - -func (bind *BindingHookGet[T]) JawsGetLocked(elem *Element) T { - return bind.BindGetHook(bind.Binder, elem) -} - -func (bind *BindingHookGet[T]) JawsGet(elem *Element) T { - bind.RLock() - defer bind.RUnlock() - return bind.JawsGetLocked(elem) -} - -func (bind *BindingHookGet[T]) JawsGetString(elem *Element) string { - return any(bind.JawsGet(elem)).(string) -} - -func (bind *BindingHookGet[T]) JawsGetFloat(elem *Element) float64 { - return any(bind.JawsGet(elem)).(float64) -} - -func (bind *BindingHookGet[T]) JawsGetBool(elem *Element) bool { - return any(bind.JawsGet(elem)).(bool) -} - -func (bind *BindingHookGet[T]) JawsGetTime(elem *Element) time.Time { - return any(bind.JawsGet(elem)).(time.Time) -} - -func (bind *BindingHookGet[T]) SetHook(setFn BindSetHook[T]) Binder[T] { - return &BindingHookSet[T]{ - Binder: bind, - BindSetHook: setFn, - } -} - -func (bind *BindingHookGet[T]) GetHook(setFn BindGetHook[T]) Binder[T] { - return &BindingHookGet[T]{ - Binder: bind, - BindGetHook: setFn, - } -} - -func (bind *BindingHookGet[T]) Success(setFn BindSuccessHook) Binder[T] { - return &BindingHookSet[T]{ - Binder: bind, - BindSuccessHook: setFn, - } -} diff --git a/bindinghookset.go b/bindinghookset.go deleted file mode 100644 index d4e3f87..0000000 --- a/bindinghookset.go +++ /dev/null @@ -1,68 +0,0 @@ -package jaws - -import "time" - -type BindingHookSet[T comparable] struct { - Binder[T] - BindSetHook[T] - BindSuccessHook -} - -func (bind *BindingHookSet[T]) JawsSetLocked(elem *Element, value T) error { - if bind.BindSetHook != nil { - return bind.BindSetHook(bind.Binder, elem, value) - } - return bind.Binder.JawsSetLocked(elem, value) -} - -func (bind *BindingHookSet[T]) jawsSetLocking(elem *Element, value T) (err error) { - bind.Lock() - defer bind.Unlock() - return bind.JawsSetLocked(elem, value) -} - -func (bind *BindingHookSet[T]) JawsSet(elem *Element, value T) (err error) { - if err = bind.jawsSetLocking(elem, value); err == nil { - if bind.BindSuccessHook != nil { - bind.BindSuccessHook() - } - } - return -} - -func (bind *BindingHookSet[T]) JawsSetString(e *Element, val string) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind *BindingHookSet[T]) JawsSetFloat(e *Element, val float64) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind *BindingHookSet[T]) JawsSetBool(e *Element, val bool) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind *BindingHookSet[T]) JawsSetTime(elem *Element, value time.Time) error { - return bind.JawsSet(elem, any(value).(T)) -} - -func (bind *BindingHookSet[T]) SetHook(setFn BindSetHook[T]) Binder[T] { - return &BindingHookSet[T]{ - Binder: bind, - BindSetHook: setFn, - } -} - -func (bind *BindingHookSet[T]) GetHook(setFn BindGetHook[T]) Binder[T] { - return &BindingHookGet[T]{ - Binder: bind, - BindGetHook: setFn, - } -} - -func (bind *BindingHookSet[T]) Success(setFn BindSuccessHook) Binder[T] { - return &BindingHookSet[T]{ - Binder: bind, - BindSuccessHook: setFn, - } -} From e4869090f89852091a833032f01a17411f42fca6 Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Fri, 6 Dec 2024 11:14:58 +0100 Subject: [PATCH 3/4] wip --- binder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binder.go b/binder.go index 8ba8eb0..783fb43 100644 --- a/binder.go +++ b/binder.go @@ -35,7 +35,7 @@ type Binder[T comparable] interface { Setter[T] JawsGetTag(*Request) any - JawsBinderPrev() Binder[T] // returns the previous Binder in the chail, or nil + JawsBinderPrev() Binder[T] // returns the previous Binder in the chain, or nil JawsGetLocked(elem *Element) (value T) JawsSetLocked(elem *Element, value T) (err error) From b8afc85328aafef3d00b6894535c2f5c30785f14 Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Fri, 6 Dec 2024 11:23:16 +0100 Subject: [PATCH 4/4] wip --- bind_test.go | 8 ++++---- binder.go | 6 ++++++ binding.go | 6 ++++++ bindinghook.go | 6 ++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/bind_test.go b/bind_test.go index d206db2..04e49f6 100644 --- a/bind_test.go +++ b/bind_test.go @@ -272,7 +272,7 @@ func TestBindFunc_String(t *testing.T) { testBind_Hooks(t, "foo") testBind_StringSetter(t, Bind(&mu, &val).(StringSetter)) - testBind_StringSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(StringSetter)) + testBind_StringSetter(t, Bind(&mu, &val).Success(func() {}).(StringSetter)) } func testBind_FloatSetter(t *testing.T, v FloatSetter) { @@ -291,7 +291,7 @@ func TestBindFunc_Float(t *testing.T) { testBind_Hooks(t, float64(1.23)) testBind_FloatSetter(t, Bind(&mu, &val).(FloatSetter)) - testBind_FloatSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(FloatSetter)) + testBind_FloatSetter(t, Bind(&mu, &val).Success(func() {}).(FloatSetter)) } func testBind_BoolSetter(t *testing.T, v BoolSetter) { @@ -310,7 +310,7 @@ func TestBindFunc_Bool(t *testing.T) { testBind_Hooks(t, true) testBind_BoolSetter(t, Bind(&mu, &val).(BoolSetter)) - testBind_BoolSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(BoolSetter)) + testBind_BoolSetter(t, Bind(&mu, &val).Success(func() {}).(BoolSetter)) } func testBind_TimeSetter(t *testing.T, v TimeSetter) { @@ -329,5 +329,5 @@ func TestBindFunc_Time(t *testing.T) { testBind_Hooks(t, time.Now()) testBind_TimeSetter(t, Bind(&mu, &val).(TimeSetter)) - testBind_TimeSetter(t, Bind(&mu, &val).Success(func() (err error) { return }).(TimeSetter)) + testBind_TimeSetter(t, Bind(&mu, &val).Success(func() {}).(TimeSetter)) } diff --git a/binder.go b/binder.go index 783fb43..f8f97b4 100644 --- a/binder.go +++ b/binder.go @@ -43,12 +43,18 @@ type Binder[T comparable] interface { // // The lock will be held at this point. // Do not lock or unlock the Binder within fn. Do not call JawsSet. + // + // The bind argument to the function is the previous Binder in the chain, + // and you probably want to call it's JawsSetLocked first. SetLocked(fn BindSetHook[T]) (newbind Binder[T]) // GetLocked returns a Binder[T] that will call fn instead of JawsGetLocked. // // The lock will be held at this point, preferring RLock over Lock, if available. // Do not lock or unlock the Binder within fn. Do not call JawsGet. + // + // The bind argument to the function is the previous Binder in the chain, + // and you probably want to call it's JawsGetLocked first. GetLocked(fn BindGetHook[T]) (newbind Binder[T]) // Success returns a Binder[T] that will call fn after the value has been set diff --git a/binding.go b/binding.go index aee24c2..c930826 100644 --- a/binding.go +++ b/binding.go @@ -106,6 +106,9 @@ func (bind Binding[T]) RUnlock() { // // The lock will be held at this point. // Do not lock or unlock the Binder within fn. Do not call JawsSet. +// +// The bind argument to the function is the previous Binder in the chain, +// and you probably want to call it's JawsSetLocked first. func (bind Binding[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { return &BindingHook[T]{ Binder: bind, @@ -117,6 +120,9 @@ func (bind Binding[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { // // The lock will be held at this point, preferring RLock over Lock, if available. // Do not lock or unlock the Binder within fn. Do not call JawsGet. +// +// The bind argument to the function is the previous Binder in the chain, +// and you probably want to call it's JawsGetLocked first. func (bind Binding[T]) GetLocked(setFn BindGetHook[T]) Binder[T] { return &BindingHook[T]{ Binder: bind, diff --git a/bindinghook.go b/bindinghook.go index c9eb854..1ce2980 100644 --- a/bindinghook.go +++ b/bindinghook.go @@ -98,6 +98,9 @@ func (bind *BindingHook[T]) JawsSetTime(elem *Element, value time.Time) error { // // The lock will be held at this point. // Do not lock or unlock the Binder within fn. Do not call JawsSet. +// +// The bind argument to the function is the previous Binder in the chain, +// and you probably want to call it's JawsSetLocked first. func (bind *BindingHook[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { return &BindingHook[T]{ Binder: bind, @@ -109,6 +112,9 @@ func (bind *BindingHook[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { // // The lock will be held at this point, preferring RLock over Lock, if available. // Do not lock or unlock the Binder within fn. Do not call JawsGet. +// +// The bind argument to the function is the previous Binder in the chain, +// and you probably want to call it's JawsGetLocked first. func (bind *BindingHook[T]) GetLocked(setFn BindGetHook[T]) Binder[T] { return &BindingHook[T]{ Binder: bind,