mcmp: make thread-safe, add InheritedValue, refactor Context/Annotation related methods
This commit is contained in:
parent
dedf3a0368
commit
7f9b0d5591
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user