-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from ccremer/context-map
Add wrapper to store and retrieve values in context
- Loading branch information
Showing
2 changed files
with
119 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |