1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package label
6
7import (
8	"fmt"
9	"io"
10	"reflect"
11	"unsafe"
12)
13
14// Key is used as the identity of a Label.
15// Keys are intended to be compared by pointer only, the name should be unique
16// for communicating with external systems, but it is not required or enforced.
17type Key interface {
18	// Name returns the key name.
19	Name() string
20	// Description returns a string that can be used to describe the value.
21	Description() string
22
23	// Format is used in formatting to append the value of the label to the
24	// supplied buffer.
25	// The formatter may use the supplied buf as a scratch area to avoid
26	// allocations.
27	Format(w io.Writer, buf []byte, l Label)
28}
29
30// Label holds a key and value pair.
31// It is normally used when passing around lists of labels.
32type Label struct {
33	key     Key
34	packed  uint64
35	untyped interface{}
36}
37
38// Map is the interface to a collection of Labels indexed by key.
39type Map interface {
40	// Find returns the label that matches the supplied key.
41	Find(key Key) Label
42}
43
44// List is the interface to something that provides an iterable
45// list of labels.
46// Iteration should start from 0 and continue until Valid returns false.
47type List interface {
48	// Valid returns true if the index is within range for the list.
49	// It does not imply the label at that index will itself be valid.
50	Valid(index int) bool
51	// Label returns the label at the given index.
52	Label(index int) Label
53}
54
55// list implements LabelList for a list of Labels.
56type list struct {
57	labels []Label
58}
59
60// filter wraps a LabelList filtering out specific labels.
61type filter struct {
62	keys       []Key
63	underlying List
64}
65
66// listMap implements LabelMap for a simple list of labels.
67type listMap struct {
68	labels []Label
69}
70
71// mapChain implements LabelMap for a list of underlying LabelMap.
72type mapChain struct {
73	maps []Map
74}
75
76// OfValue creates a new label from the key and value.
77// This method is for implementing new key types, label creation should
78// normally be done with the Of method of the key.
79func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} }
80
81// UnpackValue assumes the label was built using LabelOfValue and returns the value
82// that was passed to that constructor.
83// This method is for implementing new key types, for type safety normal
84// access should be done with the From method of the key.
85func (t Label) UnpackValue() interface{} { return t.untyped }
86
87// Of64 creates a new label from a key and a uint64. This is often
88// used for non uint64 values that can be packed into a uint64.
89// This method is for implementing new key types, label creation should
90// normally be done with the Of method of the key.
91func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} }
92
93// Unpack64 assumes the label was built using LabelOf64 and returns the value that
94// was passed to that constructor.
95// This method is for implementing new key types, for type safety normal
96// access should be done with the From method of the key.
97func (t Label) Unpack64() uint64 { return t.packed }
98
99// OfString creates a new label from a key and a string.
100// This method is for implementing new key types, label creation should
101// normally be done with the Of method of the key.
102func OfString(k Key, v string) Label {
103	hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
104	return Label{
105		key:     k,
106		packed:  uint64(hdr.Len),
107		untyped: unsafe.Pointer(hdr.Data),
108	}
109}
110
111// UnpackString assumes the label was built using LabelOfString and returns the
112// value that was passed to that constructor.
113// This method is for implementing new key types, for type safety normal
114// access should be done with the From method of the key.
115func (t Label) UnpackString() string {
116	var v string
117	hdr := (*reflect.StringHeader)(unsafe.Pointer(&v))
118	hdr.Data = uintptr(t.untyped.(unsafe.Pointer))
119	hdr.Len = int(t.packed)
120	return *(*string)(unsafe.Pointer(hdr))
121}
122
123// Valid returns true if the Label is a valid one (it has a key).
124func (t Label) Valid() bool { return t.key != nil }
125
126// Key returns the key of this Label.
127func (t Label) Key() Key { return t.key }
128
129// Format is used for debug printing of labels.
130func (t Label) Format(f fmt.State, r rune) {
131	if !t.Valid() {
132		io.WriteString(f, `nil`)
133		return
134	}
135	io.WriteString(f, t.Key().Name())
136	io.WriteString(f, "=")
137	var buf [128]byte
138	t.Key().Format(f, buf[:0], t)
139}
140
141func (l *list) Valid(index int) bool {
142	return index >= 0 && index < len(l.labels)
143}
144
145func (l *list) Label(index int) Label {
146	return l.labels[index]
147}
148
149func (f *filter) Valid(index int) bool {
150	return f.underlying.Valid(index)
151}
152
153func (f *filter) Label(index int) Label {
154	l := f.underlying.Label(index)
155	for _, f := range f.keys {
156		if l.Key() == f {
157			return Label{}
158		}
159	}
160	return l
161}
162
163func (lm listMap) Find(key Key) Label {
164	for _, l := range lm.labels {
165		if l.Key() == key {
166			return l
167		}
168	}
169	return Label{}
170}
171
172func (c mapChain) Find(key Key) Label {
173	for _, src := range c.maps {
174		l := src.Find(key)
175		if l.Valid() {
176			return l
177		}
178	}
179	return Label{}
180}
181
182var emptyList = &list{}
183
184func NewList(labels ...Label) List {
185	if len(labels) == 0 {
186		return emptyList
187	}
188	return &list{labels: labels}
189}
190
191func Filter(l List, keys ...Key) List {
192	if len(keys) == 0 {
193		return l
194	}
195	return &filter{keys: keys, underlying: l}
196}
197
198func NewMap(labels ...Label) Map {
199	return listMap{labels: labels}
200}
201
202func MergeMaps(srcs ...Map) Map {
203	var nonNil []Map
204	for _, src := range srcs {
205		if src != nil {
206			nonNil = append(nonNil, src)
207		}
208	}
209	if len(nonNil) == 1 {
210		return nonNil[0]
211	}
212	return mapChain{maps: nonNil}
213}
214