mctx: add Annotated and polish documentation
This commit is contained in:
parent
a07df614d8
commit
d5044ad7cb
@ -63,14 +63,19 @@ func Annotate(ctx context.Context, kvs ...interface{}) context.Context {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Annotated is a shortcut for calling Annotate with a context.Background().
|
||||||
|
func Annotated(kvs ...interface{}) context.Context {
|
||||||
|
return Annotate(context.Background(), kvs...)
|
||||||
|
}
|
||||||
|
|
||||||
// AnnotationSet describes a set of unique Annotation values which were
|
// AnnotationSet describes a set of unique Annotation values which were
|
||||||
// retrieved off a Context via the Annotations function. An AnnotationSet has a
|
// retrieved off a Context via the Annotations function. An AnnotationSet has a
|
||||||
// couple methods on it to aid in post-processing.
|
// couple methods on it to aid in post-processing.
|
||||||
type AnnotationSet []Annotation
|
type AnnotationSet []Annotation
|
||||||
|
|
||||||
// Annotations returns all Annotation values which have been set via Annotate on
|
// Annotations returns all Annotation values which have been set via Annotate on
|
||||||
// this Context. If a key was set twice then only the most recent value is
|
// this Context and its ancestors. If a key was set twice then only the most
|
||||||
// included.
|
// recent value is included.
|
||||||
func Annotations(ctx context.Context) AnnotationSet {
|
func Annotations(ctx context.Context) AnnotationSet {
|
||||||
a, _ := ctx.Value(annotationKey(0)).(*annotation)
|
a, _ := ctx.Value(annotationKey(0)).(*annotation)
|
||||||
if a == nil {
|
if a == nil {
|
||||||
@ -272,9 +277,10 @@ func mergeAnnotations(ctxA, ctxB context.Context) context.Context {
|
|||||||
|
|
||||||
// MergeAnnotations sequentially merges the annotation data of the passed in
|
// MergeAnnotations sequentially merges the annotation data of the passed in
|
||||||
// Contexts into the first passed in one. Data from a Context overwrites
|
// Contexts into the first passed in one. Data from a Context overwrites
|
||||||
// overlapping data on all passed in Contexts to the left of it. All other
|
// overlapping data on all passed in Contexts to the left of it (keeping in mind
|
||||||
// aspects of the first Context remain the same, and that Context is returned
|
// that two Annotations must share the same Key _and_ Path to overlap). All
|
||||||
// with the new set of Annotation data.
|
// other aspects of the first Context remain the same, and that Context is
|
||||||
|
// returned with the new set of Annotation data.
|
||||||
//
|
//
|
||||||
// NOTE this will panic if no Contexts are passed in.
|
// NOTE this will panic if no Contexts are passed in.
|
||||||
func MergeAnnotations(ctxs ...context.Context) context.Context {
|
func MergeAnnotations(ctxs ...context.Context) context.Context {
|
||||||
|
90
mctx/ctx.go
90
mctx/ctx.go
@ -1,12 +1,62 @@
|
|||||||
// Package mctx extends the builtin context package to organize Contexts into a
|
// Package mctx extends the builtin context package to organize Contexts into a
|
||||||
// hierarchy. Each node in the hierarchy is given a name and is aware of all of
|
// hierarchy.
|
||||||
// its ancestors.
|
|
||||||
//
|
|
||||||
// This package also provides extra functionality which allows contexts
|
|
||||||
// to be more useful when used in the hierarchy.
|
|
||||||
//
|
//
|
||||||
// All functions and methods in this package are thread-safe unless otherwise
|
// All functions and methods in this package are thread-safe unless otherwise
|
||||||
// noted.
|
// noted.
|
||||||
|
//
|
||||||
|
// Parents and children
|
||||||
|
//
|
||||||
|
// Each node in the hierarchy is given a name and is aware of all of its
|
||||||
|
// ancestors. The sequence of ancestor's names, ending in the node's name, is
|
||||||
|
// called its "path". For example:
|
||||||
|
//
|
||||||
|
// ctx := context.Background()
|
||||||
|
// ctxA := mctx.NewChild(ctx, "A")
|
||||||
|
// ctxB := mctx.NewChild(ctx, "B")
|
||||||
|
// fmt.Printf("ctx:%#v\n", mctx.Path(ctx)) // prints "ctx:[]string(nil)"
|
||||||
|
// fmt.Printf("ctxA:%#v\n", mctx.Path(ctxA)) // prints "ctxA:[]string{"A"}
|
||||||
|
// fmt.Printf("ctxB:%#v\n", mctx.Path(ctxB)) // prints "ctxA:[]string{"A", "B"}
|
||||||
|
//
|
||||||
|
// WithChild can be used to incorporate a child into its parent, making the
|
||||||
|
// parent's children iterable on it:
|
||||||
|
//
|
||||||
|
// ctx := context.Background()
|
||||||
|
// ctxA1 := mctx.NewChild(ctx, "A1")
|
||||||
|
// ctxA2 := mctx.NewChild(ctx, "A2")
|
||||||
|
// ctx = mctx.WithChild(ctx, ctxA1)
|
||||||
|
// ctx = mctx.WithChild(ctx, ctxA2)
|
||||||
|
// for _, childCtx := range mctx.Children(ctx) {
|
||||||
|
// fmt.Printf("%q\n", mctx.Name(childCtx)) // prints "A1" then "A2"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Key/Value
|
||||||
|
//
|
||||||
|
// The context's key/value namespace is split into two: a space local to a node,
|
||||||
|
// not inherited from its parent or inheritable by its children
|
||||||
|
// (WithLocalValue), and the original one which comes with the builtin context
|
||||||
|
// package (context.WithValue):
|
||||||
|
//
|
||||||
|
// ctx := context.Background()
|
||||||
|
// ctx = context.WithValue(ctx, "inheritableKey", "foo")
|
||||||
|
// ctx = mctx.WithLocalValue(ctx, "localKey", "bar")
|
||||||
|
// childCtx := mctx.NewChild(ctx, "child")
|
||||||
|
//
|
||||||
|
// // ctx.Value("inheritableKey") == "foo"
|
||||||
|
// // child.Value("inheritableKey") == "foo"
|
||||||
|
// // mctx.LocalValue(ctx, "localKey") == "bar"
|
||||||
|
// // mctx.LocalValue(child, "localKey") == nil
|
||||||
|
//
|
||||||
|
// Annotations
|
||||||
|
//
|
||||||
|
// Annotations are a special case of local key/values, where the data being
|
||||||
|
// stored is specifically runtime metadata which would be useful for logging,
|
||||||
|
// error output, etc... Annotation data might include an IP address of a
|
||||||
|
// connected client, a userID the client has authenticated as, the primary key
|
||||||
|
// of a row in a database being queried, etc...
|
||||||
|
//
|
||||||
|
// Annotations are always tied to the path of the node they were set on, so that
|
||||||
|
// even when the annotations of two contexts are merged the annotation data will
|
||||||
|
// not overlap unless the contexts have the same path.
|
||||||
package mctx
|
package mctx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -41,9 +91,9 @@ func Child(parent context.Context, name string) context.Context {
|
|||||||
return parent.Value(ancestryKeyChildren).([]context.Context)[i]
|
return parent.Value(ancestryKeyChildren).([]context.Context)[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Children returns all children of this Context which have been kept by
|
// Children returns all children of this Context which have been added by
|
||||||
// WithChild, mapped by their name. If this Context wasn't produced by WithChild
|
// WithChild, in the order they were added. If this Context wasn't produced by
|
||||||
// then this returns nil.
|
// WithChild then this returns nil.
|
||||||
func Children(parent context.Context) []context.Context {
|
func Children(parent context.Context) []context.Context {
|
||||||
children, _ := parent.Value(ancestryKeyChildren).([]context.Context)
|
children, _ := parent.Value(ancestryKeyChildren).([]context.Context)
|
||||||
return children
|
return children
|
||||||
@ -101,8 +151,8 @@ func pathHash(path []string) string {
|
|||||||
return hex.EncodeToString(pathHash.Sum(nil))
|
return hex.EncodeToString(pathHash.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the name this Context was generated with via NewChild, or false
|
// Name returns the name this Context was created with via NewChild, or false if
|
||||||
// if this Context was not generated via NewChild.
|
// this Context was not created via NewChild.
|
||||||
func Name(ctx context.Context) (string, bool) {
|
func Name(ctx context.Context) (string, bool) {
|
||||||
path := Path(ctx)
|
path := Path(ctx)
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
@ -111,10 +161,10 @@ func Name(ctx context.Context) (string, bool) {
|
|||||||
return path[len(path)-1], true
|
return path[len(path)-1], true
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChild creates a new Context based off of the parent one, and returns a new
|
// NewChild creates and returns a new Context based off of the parent one. The
|
||||||
// instance of the passed in parent and the new child. The child will have a
|
// child will have a path which is the parent's path appended with the given
|
||||||
// path which is the parent's path with the given name appended. The parent will
|
// name. In order for the parent to "see" the child (via the Child or Children
|
||||||
// have the new child as part of its set of children (see Children function).
|
// functions) the WithChild function must be used.
|
||||||
//
|
//
|
||||||
// If the parent already has a child of the given name this function panics.
|
// If the parent already has a child of the given name this function panics.
|
||||||
func NewChild(parent context.Context, name string) context.Context {
|
func NewChild(parent context.Context, name string) context.Context {
|
||||||
@ -147,7 +197,7 @@ func isChild(parent, child context.Context) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithChild returns a modified parent which holds a reference to child in its
|
// WithChild returns a modified parent which holds a reference to child in its
|
||||||
// Children set. If the child's name is already taken in the parent then this
|
// Children list. If the child's name is already taken in the parent then this
|
||||||
// function panics.
|
// function panics.
|
||||||
func WithChild(parent, child context.Context) context.Context {
|
func WithChild(parent, child context.Context) context.Context {
|
||||||
if !isChild(parent, child) {
|
if !isChild(parent, child) {
|
||||||
@ -168,8 +218,8 @@ func WithChild(parent, child context.Context) context.Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BreadthFirstVisit visits this Context and all of its children, and their
|
// BreadthFirstVisit visits this Context and all of its children, and their
|
||||||
// children, in a breadth-first order. If the callback returns false then the
|
// children, etc... in a breadth-first order. If the callback returns false then
|
||||||
// function returns without visiting any more Contexts.
|
// the function returns without visiting any more Contexts.
|
||||||
func BreadthFirstVisit(ctx context.Context, callback func(context.Context) bool) {
|
func BreadthFirstVisit(ctx context.Context, callback func(context.Context) bool) {
|
||||||
queue := []context.Context{ctx}
|
queue := []context.Context{ctx}
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
@ -193,9 +243,9 @@ type localVal struct {
|
|||||||
key, val interface{}
|
key, val interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLocalValue is like WithValue, but the stored value will not be present
|
// WithLocalValue is like context.WithValue, but the stored value will not be
|
||||||
// on any children created via WithChild. Local values must be retrieved with
|
// present on any children created via NewChild. Local values must be retrieved
|
||||||
// the LocalValue function in this package. Local values share a different
|
// with the LocalValue function in this package. Local values share a different
|
||||||
// namespace than the normal WithValue/Value values (i.e. they do not overlap).
|
// namespace than the normal WithValue/Value values (i.e. they do not overlap).
|
||||||
func WithLocalValue(ctx context.Context, key, val interface{}) context.Context {
|
func WithLocalValue(ctx context.Context, key, val interface{}) context.Context {
|
||||||
prev, _ := ctx.Value(localValsKey(0)).(*localVal)
|
prev, _ := ctx.Value(localValsKey(0)).(*localVal)
|
||||||
|
Loading…
Reference in New Issue
Block a user