stack_values.go (2170B)
1 package middleware 2 3 import ( 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. 12 func 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. 20 func 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. 26 func 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 35 type stackValuesKey struct{} 36 37 type stackValues struct { 38 key interface{} 39 value interface{} 40 parent *stackValues 41 } 42 43 func 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 53 func (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 65 func (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 85 type 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(). 92 func 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 }