From f87dfa4f89978619b364a9277d50b97568d2aefb Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 27 Dec 2021 12:29:11 +0100 Subject: [PATCH] Add option to disable error wrapping Sometimes implementations may want to transparently run code without wrapping errors from a library. --- options.go | 21 +++++++++++++++++++++ options_test.go | 22 ++++++++++++++++++++++ pipeline.go | 14 +++++++++----- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 options.go create mode 100644 options_test.go diff --git a/options.go b/options.go new file mode 100644 index 0000000..b4ed5a9 --- /dev/null +++ b/options.go @@ -0,0 +1,21 @@ +package pipeline + +// Option configures the given Pipeline with a behaviour-altering setting. +type Option func(pipeline *Pipeline) + +// WithOptions configures the Pipeline with settings. +// The options are applied immediately. +// Options are applied to nested pipelines provided they are set before building the nested pipeline. +// Nested pipelines can be configured with their own options. +func (p *Pipeline) WithOptions(options ...Option) *Pipeline { + for _, option := range options { + option(p) + } + return p +} + +// DisableErrorWrapping disables the wrapping of errors that are emitted from pipeline steps. +// This effectively causes Result.Err to be exactly the error as returned from a step. +var DisableErrorWrapping Option = func(pipeline *Pipeline) { + pipeline.disableErrorWrapping = true +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..775c238 --- /dev/null +++ b/options_test.go @@ -0,0 +1,22 @@ +package pipeline + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPipeline_WithOptions(t *testing.T) { + t.Run("DisableErrorWrapping", func(t *testing.T) { + p := NewPipeline().WithOptions(DisableErrorWrapping) + p.WithSteps( + NewStepFromFunc("disabled error wrapping", func(_ Context) error { + return errors.New("some error") + }), + ) + assert.True(t, p.disableErrorWrapping) + result := p.Run() + assert.Equal(t, "some error", result.Err.Error()) + }) +} diff --git a/pipeline.go b/pipeline.go index 4a2afce..f5e6f60 100644 --- a/pipeline.go +++ b/pipeline.go @@ -8,10 +8,11 @@ import ( type ( // Pipeline holds and runs intermediate actions, called "steps". Pipeline struct { - steps []Step - context Context - beforeHooks []Listener - finalizer ResultHandler + steps []Step + context Context + beforeHooks []Listener + finalizer ResultHandler + disableErrorWrapping bool } // Result is the object that is returned after each step and after running a pipeline. Result struct { @@ -87,7 +88,7 @@ func (p *Pipeline) WithSteps(steps ...Step) *Pipeline { // WithNestedSteps is similar to AsNestedStep, but it accepts the steps given directly as parameters. func (p *Pipeline) WithNestedSteps(name string, steps ...Step) Step { return NewStep(name, func(_ Context) Result { - nested := &Pipeline{beforeHooks: p.beforeHooks, steps: steps, context: p.context} + nested := &Pipeline{beforeHooks: p.beforeHooks, steps: steps, context: p.context, disableErrorWrapping: p.disableErrorWrapping} return nested.Run() }) } @@ -145,6 +146,9 @@ func (p *Pipeline) doRun() Result { // Abort pipeline without error return Result{} } + if p.disableErrorWrapping { + return Result{Err: err} + } return Result{Err: fmt.Errorf("step '%s' failed: %w", step.Name, err)} } }