diff --git a/mcfg/cli.go b/mcfg/cli.go index e8c7b4f..522acf9 100644 --- a/mcfg/cli.go +++ b/mcfg/cli.go @@ -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) } diff --git a/mcfg/mcfg.go b/mcfg/mcfg.go index f834cb0..926e685 100644 --- a/mcfg/mcfg.go +++ b/mcfg/mcfg.go @@ -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) } diff --git a/mcmp/component.go b/mcmp/component.go index 26c7b3c..1b537fa 100644 --- a/mcmp/component.go +++ b/mcmp/component.go @@ -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{} + 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 diff --git a/mcmp/component_test.go b/mcmp/component_test.go index e1ebb77..aa41ca3 100644 --- a/mcmp/component_test.go +++ b/mcmp/component_test.go @@ -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)