Skip to content

Commit

Permalink
Merge pull request #39 from ccremer/context-map
Browse files Browse the repository at this point in the history
Add wrapper to store and retrieve values in context
  • Loading branch information
ccremer committed Apr 12, 2022
2 parents 24ec2f9 + d0821c2 commit becd0df
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 0 deletions.
47 changes: 47 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package pipeline

import (
"context"
"errors"
"sync"
)

type contextKey struct{}

// VariableContext adds a map to the given context that can be used to store intermediate values in the context.
// It uses sync.Map under the hood.
//
// See also AddToContext() and ValueFromContext.
func VariableContext(parent context.Context) context.Context {
return context.WithValue(parent, contextKey{}, &sync.Map{})
}

// AddToContext adds the given key and value to ctx.
// Any keys or values added during pipeline execution is available in the next steps, provided the pipeline runs synchronously.
// In parallel executed pipelines you may encounter race conditions.
// Use ValueFromContext to retrieve values.
//
// Note: This method is thread-safe, but panics if ctx has not been set up with VariableContext first.
func AddToContext(ctx context.Context, key, value interface{}) {
m := ctx.Value(contextKey{})
if m == nil {
panic(errors.New("context was not set up with VariableContext()"))
}
m.(*sync.Map).Store(key, value)
}

// ValueFromContext returns the value from the given context with the given key.
// It returns the value and true, or nil and false if the key doesn't exist.
// It may return nil and true if the key exists, but the value actually is nil.
// Use AddToContext to store values.
//
// Note: This method is thread-safe, but panics if the ctx has not been set up with VariableContext first.
func ValueFromContext(ctx context.Context, key interface{}) (interface{}, bool) {
m := ctx.Value(contextKey{})
if m == nil {
panic(errors.New("context was not set up with VariableContext()"))
}
mp := m.(*sync.Map)
val, found := mp.Load(key)
return val, found
}
72 changes: 72 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package pipeline

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestContext(t *testing.T) {
tests := map[string]struct {
givenKey interface{}
givenValue interface{}
expectedValue interface{}
expectedFound bool
}{
"GivenNonExistentKey_ThenExpectNilAndFalse": {
givenKey: nil,
expectedValue: nil,
},
"GivenKeyWithNilValue_ThenExpectNilAndTrue": {
givenKey: "key",
givenValue: nil,
expectedValue: nil,
expectedFound: true,
},
"GivenKeyWithValue_ThenExpectValueAndTrue": {
givenKey: "key",
givenValue: "value",
expectedValue: "value",
expectedFound: true,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
ctx := VariableContext(context.Background())
if tc.givenKey != nil {
AddToContext(ctx, tc.givenKey, tc.givenValue)
}
result, found := ValueFromContext(ctx, tc.givenKey)
assert.Equal(t, tc.expectedValue, result, "value")
assert.Equal(t, tc.expectedFound, found, "value found")
})
}
}

func TestContextPanics(t *testing.T) {
assert.PanicsWithError(t, "context was not set up with VariableContext()", func() {
AddToContext(context.Background(), "key", "value")
}, "AddToContext")
assert.PanicsWithError(t, "context was not set up with VariableContext()", func() {
ValueFromContext(context.Background(), "key")
}, "ValueFromContext")
}

func ExampleVariableContext() {
ctx := VariableContext(context.Background())
p := NewPipeline().WithSteps(
NewStepFromFunc("store value", func(ctx context.Context) error {
AddToContext(ctx, "key", "value")
return nil
}),
NewStepFromFunc("retrieve value", func(ctx context.Context) error {
value, _ := ValueFromContext(ctx, "key")
fmt.Println(value)
return nil
}),
)
p.RunWithContext(ctx)
// Output: value
}

0 comments on commit becd0df

Please sign in to comment.