Skip to content

Commit

Permalink
Merge pull request #5 from ssgreg/feature/with
Browse files Browse the repository at this point in the history
Feature/with
  • Loading branch information
ssgreg authored Feb 6, 2020
2 parents 8b8c013 + 08ca47d commit 536dbd8
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 26 deletions.
58 changes: 32 additions & 26 deletions repeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,30 @@ type Repeater interface {
}

type stdRepeater struct {
wop OpWrapper
woop OpWrapper
c Operation
d Operation
opw OpWrapper
copw OpWrapper
}

// NewRepeater sets up everything to be able to repeat operations.
func NewRepeater() Repeater {
return &stdRepeater{Forward, Forward, Done, Done}
return NewRepeaterExt(Forward, Forward)
}

// Wrap returns object that wraps all repeating ops with passed OpWrapper.
func Wrap(wop OpWrapper) Repeater {
return &stdRepeater{wop, Forward, Done, Done}
func Wrap(opw OpWrapper) Repeater {
return NewRepeaterExt(opw, Forward)
}

// WrapOnce returns object that wraps all repeating ops combined into a single op
// with passed OpWrapper calling it once.
func WrapOnce(wop OpWrapper) Repeater {
return &stdRepeater{Forward, wop, Done, Done}
// WrapOnce returns object that wraps all repeating ops combined into a single
// op with passed OpWrapper calling it once.
func WrapOnce(copw OpWrapper) Repeater {
return NewRepeaterExt(Forward, copw)
}

// NewRepeaterExt returns object that wraps all ops with with the given opw
// and wraps composed operation with the given copw.
func NewRepeaterExt(opw, copw OpWrapper) Repeater {
return &stdRepeater{opw, copw}
}

// Cpp returns object that calls C (constructor) at first, then ops,
Expand All @@ -77,8 +81,21 @@ func WrapOnce(wop OpWrapper) Repeater {
// Note! Cpp panics if D returns non nil error. Wrap it using Done if
// you log D's error or handle it somehow else.
//
func Cpp(c Operation, d Operation) Repeater {
return &stdRepeater{Forward, Forward, c, FnPanic(d)}
func Cpp(c, d Operation) Repeater {
return NewRepeaterExt(Forward, WrWith(c, func(e error) error {
_ = FnPanic(d)(e)

return e
}))
}

// With returns object that calls C (constructor) at first, then ops,
// then D (destructor). D will be called in any case if C returns nil.
//
// Note! D is able to hide original error an return nil or return error
// event if the original error is nil.
func With(c, d Operation) Repeater {
return NewRepeaterExt(Forward, WrWith(c, d))
}

// Once composes the operations and executes the result once.
Expand Down Expand Up @@ -124,20 +141,9 @@ func (w *stdRepeater) FnRepeat(ops ...Operation) Operation {
// Compose wraps ops with wop and composes all passed operations info
// a single one.
func (w *stdRepeater) Compose(ops ...Operation) Operation {
return w.woop(func(e error) (err error) {
err = w.c(e)
if err != nil {
// If C failed with temporary error, stop error or any other
// error: stop compose with this error.
return err
}
defer func() {
// Note: handle error using D wrapper.
_ = w.d(err)
}()

return w.copw(func(e error) (err error) {
for _, op := range ops {
err = w.wop(op)(e)
err = w.opw(op)(e)
switch err.(type) {
// Replace last E with nil.
case nil:
Expand Down
75 changes: 75 additions & 0 deletions repeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,81 @@ func TestCpp_TransparentC(t *testing.T) {
require.Equal(t, 3, c)
}

func TestWith_C_D(t *testing.T) {
c := 0

cd := func(e error) error {
c++
return e
}

require.NoError(t, With(cd, cd).Compose(Nope)(nil))
require.Equal(t, 2, c)
}

func TestWith_C_NoD(t *testing.T) {
c := 0

cd := func(e error) error {
c++
return e
}

require.EqualError(t, Cpp(cd, cd).Compose(Nope)(errGolden), errGolden.Error())
require.Equal(t, 1, c)
}

func TestWith_C_PanicOP_D(t *testing.T) {
c := 0

cd := func(e error) error {
c++
return e
}

errOp := func(error) error {
panic(errGolden)
}

require.Panics(t, func() {
With(cd, cd).Compose(errOp)(nil)
})
require.Equal(t, 2, c)
}

func TestWith_C_ErrOP_DoneD(t *testing.T) {
c := 0

cd := func(e error) error {
c++
return e
}

errOp := func(error) error {
return errGolden
}

require.NoError(t, With(cd, FnDone(cd)).Compose(errOp)(nil))
require.Equal(t, 2, c)
}

func TestWith_C_DoneOP_ErrD(t *testing.T) {
counter := 0

c := func(e error) error {
counter++
return e
}

d := func(error) error {
counter++
return errGolden
}

require.EqualError(t, With(c, d).Compose(Nope)(nil), errGolden.Error())
require.Equal(t, 2, counter)
}

func TestRepeatWithContext(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
Expand Down
21 changes: 21 additions & 0 deletions wrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ func WrStopOnContextError(ctx context.Context) OpWrapper {
}
}

// WrWith returns wrapper that calls C (constructor) at first, then ops,
// then D (destructor). D will be called in any case if C returns nil.
func WrWith(c, d Operation) OpWrapper {
return func(op Operation) Operation {
return func(e error) (err error) {
err = c(e)
if err != nil {
// If C failed with temporary error, stop error or any other
// error: stop compose with this error.
return err
}
defer func() {
// Note: handle error using D wrapper.
err = d(err)
}()

return op(e)
}
}
}

// Forward returns the passed operation.
func Forward(op Operation) Operation {
return op
Expand Down

0 comments on commit 536dbd8

Please sign in to comment.