stack_values.go raw

   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  }
 101