mcmp: make thread-safe, add InheritedValue, refactor Context/Annotation related methods

This commit is contained in:
Brian Picciano 2019-06-16 19:14:57 -06:00
parent dedf3a0368
commit 7f9b0d5591
4 changed files with 97 additions and 16 deletions

View File

@ -257,7 +257,7 @@ func (cli *SourceCLI) parse(
pvStrValOk = false
}
if pOk && !pvStrValOk {
ctx := mctx.Annotate(p.Component.Annotated(), "param", key)
ctx := mctx.Annotate(p.Component.Context(), "param", key)
return nil, merr.New("param expected a value", ctx)
}

View File

@ -131,7 +131,7 @@ func Populate(cmp *mcmp.Component, src Source) error {
if !p.Required {
continue
} else if _, ok := pvM[hash]; !ok {
ctx := mctx.Annotate(p.Component.Annotated(),
ctx := mctx.Annotate(p.Component.Context(),
"param", paramFullName(p.Component.Path(), p.Name))
return merr.New("required parameter is not set", ctx)
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"sync"
"github.com/mediocregopher/mediocre-go-lib/mctx"
)
@ -28,37 +29,76 @@ type child struct {
// A new Component, i.e. the root Component in the hierarchy, can be initialized
// by doing:
// new(Component).
//
// Method's on Component are thread-safe.
type Component struct {
l sync.RWMutex
path []string
parent *Component
children []child
kv map[interface{}]interface{}
ctx context.Context
}
// SetValue sets the given key to the given value on the Component, overwriting
// any previous value for that key.
func (c *Component) SetValue(key, value interface{}) {
c.l.Lock()
defer c.l.Unlock()
if c.kv == nil {
c.kv = make(map[interface{}]interface{}, 1)
}
c.kv[key] = value
}
// Value returns the value which has been set for the given key.
func (c *Component) Value(key interface{}) interface{} {
return c.kv[key]
func (c *Component) value(key interface{}) (interface{}, bool) {
c.l.RLock()
defer c.l.RUnlock()
if c.kv == nil {
return nil, false
}
value, ok := c.kv[key]
return value, ok
}
// Values returns all key/value pairs which have been set via SetValue. The
// returned map should _not_ be modified.
// Value returns the value which has been set for the given key.
func (c *Component) Value(key interface{}) interface{} {
value, _ := c.value(key)
return value
}
// InheritedValue returns the value which has been set for the given key. It first
// looks for the key on the receiver Component. If not found, it will look on
// its parent Component, and so on, until the key is found. If the key is not
// found on the root Component then false is returned.
func (c *Component) InheritedValue(key interface{}) (interface{}, bool) {
value, ok := c.value(key)
if ok {
return value, ok
} else if c.parent == nil {
return nil, false
}
return c.parent.InheritedValue(key)
}
// Values returns all key/value pairs which have been set via SetValue.
func (c *Component) Values() map[interface{}]interface{} {
return c.kv
c.l.RLock()
defer c.l.RUnlock()
out := make(map[interface{}]interface{}, len(c.kv))
for k, v := range c.kv {
out[k] = v
}
return out
}
// HasValue returns true if the given key has had a value set on it with
// SetValue.
func (c *Component) HasValue(key interface{}) bool {
c.l.RLock()
defer c.l.RUnlock()
_, ok := c.kv[key]
return ok
}
@ -69,6 +109,8 @@ func (c *Component) HasValue(key interface{}) bool {
//
// If a child of the given name has already been created this method will panic.
func (c *Component) Child(name string) *Component {
c.l.Lock()
defer c.l.Unlock()
for _, child := range c.children {
if child.name == name {
panic(fmt.Sprintf("child with name %q already exists", name))
@ -86,6 +128,8 @@ func (c *Component) Child(name string) *Component {
// Children returns all Components created via the Child method on this
// Component, in the order they were created.
func (c *Component) Children() []*Component {
c.l.RLock()
defer c.l.RUnlock()
children := make([]*Component, len(c.children))
for i := range c.children {
children[i] = c.children[i].Component
@ -97,6 +141,8 @@ func (c *Component) Children() []*Component {
// or false if this Component was not created via Child (and is therefore the
// root Component).
func (c *Component) Name() (string, bool) {
c.l.RLock()
defer c.l.RUnlock()
if len(c.path) == 0 {
return "", false
}
@ -115,11 +161,14 @@ func (c *Component) Name() (string, bool) {
// fmt.Printf("%#v\n", grandChild.Path()) // []string{"child", "grandchild"}
//
func (c *Component) Path() []string {
c.l.RLock()
defer c.l.RUnlock()
return c.path
}
func (c *Component) pathStr() string {
path := c.Path()
path := make([]string, len(c.path))
copy(path, c.path)
for i := range path {
path[i] = strings.ReplaceAll(path[i], "/", `\/`)
}
@ -128,14 +177,28 @@ func (c *Component) pathStr() string {
type annotateKey string
// Annotate annotates the given Context with information about the Component.
func (c *Component) Annotate(ctx context.Context) context.Context {
return mctx.Annotate(ctx, annotateKey("componentPath"), c.pathStr())
func (c *Component) getCtx() context.Context {
if c.ctx == nil {
c.ctx = mctx.Annotated(annotateKey("componentPath"), c.pathStr())
}
return c.ctx
}
// Annotated is a shortcut for `c.Annotate(context.Background())`.
func (c *Component) Annotated() context.Context {
return c.Annotate(context.Background())
// Annotate annotates the Component's internal Context in-place, such that they
// will be included in any future calls to the Context method.
func (c *Component) Annotate(kv ...interface{}) {
c.l.Lock()
defer c.l.Unlock()
c.ctx = mctx.Annotate(c.getCtx(), kv...)
}
// Context returns a Context which has been annotated with any annotations from
// Annotate calls to this Component, as well as some default annotations which
// are always included.
func (c *Component) Context() context.Context {
c.l.Lock()
defer c.l.Unlock()
return c.getCtx()
}
// BreadthFirstVisit visits this Component and all of its children, and their

View File

@ -52,6 +52,24 @@ func TestComponent(t *T) {
assertValue(c, "bar", nil),
assertValue(child, "bar", 2),
)
assertInheritedValue := func(c *Component, key, expectedValue interface{}) massert.Assertion {
val, ok := c.InheritedValue(key)
return massert.All(
massert.Equal(expectedValue, val),
massert.Equal(expectedValue != nil, ok),
)
}
// test that InheritedValue does what it's supposed to
massert.Require(t,
assertInheritedValue(c, "foo", 1),
assertInheritedValue(child, "foo", 1),
assertInheritedValue(c, "bar", nil),
assertInheritedValue(child, "bar", 2),
assertInheritedValue(c, "xxx", nil),
assertInheritedValue(child, "xxx", nil),
)
}
func TestBreadFirstVisit(t *T) {
cmp := new(Component)