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}