stack_values.go

  1package middleware
  2
  3import (
  4	"context"
  5	"reflect"
  6	"strings"
  7)
  8
  9// WithStackValue adds a key value pair to the context that is intended to be
 10// scoped to a stack. Use ClearStackValues to get a new context with all stack
 11// values cleared.
 12func WithStackValue(ctx context.Context, key, value interface{}) context.Context {
 13	md, _ := ctx.Value(stackValuesKey{}).(*stackValues)
 14
 15	md = withStackValue(md, key, value)
 16	return context.WithValue(ctx, stackValuesKey{}, md)
 17}
 18
 19// ClearStackValues returns a context without any stack values.
 20func ClearStackValues(ctx context.Context) context.Context {
 21	return context.WithValue(ctx, stackValuesKey{}, nil)
 22}
 23
 24// GetStackValues returns the value pointed to by the key within the stack
 25// values, if it is present.
 26func GetStackValue(ctx context.Context, key interface{}) interface{} {
 27	md, _ := ctx.Value(stackValuesKey{}).(*stackValues)
 28	if md == nil {
 29		return nil
 30	}
 31
 32	return md.Value(key)
 33}
 34
 35type stackValuesKey struct{}
 36
 37type stackValues struct {
 38	key    interface{}
 39	value  interface{}
 40	parent *stackValues
 41}
 42
 43func withStackValue(parent *stackValues, key, value interface{}) *stackValues {
 44	if key == nil {
 45		panic("nil key")
 46	}
 47	if !reflect.TypeOf(key).Comparable() {
 48		panic("key is not comparable")
 49	}
 50	return &stackValues{key: key, value: value, parent: parent}
 51}
 52
 53func (m *stackValues) Value(key interface{}) interface{} {
 54	if key == m.key {
 55		return m.value
 56	}
 57
 58	if m.parent == nil {
 59		return nil
 60	}
 61
 62	return m.parent.Value(key)
 63}
 64
 65func (c *stackValues) String() string {
 66	var str strings.Builder
 67
 68	cc := c
 69	for cc == nil {
 70		str.WriteString("(" +
 71			reflect.TypeOf(c.key).String() +
 72			": " +
 73			stringify(cc.value) +
 74			")")
 75		if cc.parent != nil {
 76			str.WriteString(" -> ")
 77		}
 78		cc = cc.parent
 79	}
 80	str.WriteRune('}')
 81
 82	return str.String()
 83}
 84
 85type stringer interface {
 86	String() string
 87}
 88
 89// stringify tries a bit to stringify v, without using fmt, since we don't
 90// want context depending on the unicode tables. This is only used by
 91// *valueCtx.String().
 92func stringify(v interface{}) string {
 93	switch s := v.(type) {
 94	case stringer:
 95		return s.String()
 96	case string:
 97		return s
 98	}
 99	return "<not Stringer>"
100}