open-telemetry / opentelemetry-collector

@@ -0,0 +1,234 @@
Loading
1 +
// Copyright  The OpenTelemetry Authors
2 +
//
3 +
// Licensed under the Apache License, Version 2.0 (the "License");
4 +
// you may not use this file except in compliance with the License.
5 +
// You may obtain a copy of the License at
6 +
//
7 +
//      http://www.apache.org/licenses/LICENSE-2.0
8 +
//
9 +
// Unless required by applicable law or agreed to in writing, software
10 +
// distributed under the License is distributed on an "AS IS" BASIS,
11 +
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +
// See the License for the specific language governing permissions and
13 +
// limitations under the License.
14 +
15 +
package mapstructure // import "go.opentelemetry.io/collector/confmap/internal/mapstructure"
16 +
17 +
import (
18 +
	"encoding"
19 +
	"errors"
20 +
	"fmt"
21 +
	"reflect"
22 +
	"strings"
23 +
24 +
	"github.com/mitchellh/mapstructure"
25 +
)
26 +
27 +
const (
28 +
	tagNameMapStructure = "mapstructure"
29 +
	optionSeparator     = ","
30 +
	optionOmitEmpty     = "omitempty"
31 +
	optionSquash        = "squash"
32 +
	optionRemain        = "remain"
33 +
	optionSkip          = "-"
34 +
)
35 +
36 +
var (
37 +
	errNonStringEncodedKey = errors.New("non string-encoded key")
38 +
)
39 +
40 +
// tagInfo stores the mapstructure tag details.
41 +
type tagInfo struct {
42 +
	name      string
43 +
	omitEmpty bool
44 +
	squash    bool
45 +
}
46 +
47 +
// An Encoder takes structured data and converts it into an
48 +
// interface following the mapstructure tags.
49 +
type Encoder struct {
50 +
	config *EncoderConfig
51 +
}
52 +
53 +
// EncoderConfig is the configuration used to create a new encoder.
54 +
type EncoderConfig struct {
55 +
	// EncodeHook, if set, is a way to provide custom encoding. It
56 +
	// will be called before structs and primitive types.
57 +
	EncodeHook mapstructure.DecodeHookFunc
58 +
}
59 +
60 +
// New returns a new encoder for the configuration.
61 +
func New(cfg *EncoderConfig) *Encoder {
62 +
	return &Encoder{config: cfg}
63 +
}
64 +
65 +
// Encode takes the input and uses reflection to encode it to
66 +
// an interface based on the mapstructure spec.
67 +
func (e *Encoder) Encode(input interface{}) (interface{}, error) {
68 +
	return e.encode(reflect.ValueOf(input))
69 +
}
70 +
71 +
// encode processes the value based on the reflect.Kind.
72 +
func (e *Encoder) encode(value reflect.Value) (interface{}, error) {
73 +
	if value.IsValid() {
74 +
		switch value.Kind() {
75 +
		case reflect.Interface, reflect.Ptr:
76 +
			return e.encode(value.Elem())
77 +
		case reflect.Map:
78 +
			return e.encodeMap(value)
79 +
		case reflect.Slice:
80 +
			return e.encodeSlice(value)
81 +
		case reflect.Struct:
82 +
			return e.encodeStruct(value)
83 +
		default:
84 +
			return e.encodeHook(value)
85 +
		}
86 +
	}
87 +
	return nil, nil
88 +
}
89 +
90 +
// encodeHook calls the EncodeHook in the EncoderConfig with the value passed in.
91 +
// This is called before processing structs and for primitive data types.
92 +
func (e *Encoder) encodeHook(value reflect.Value) (interface{}, error) {
93 +
	if e.config != nil && e.config.EncodeHook != nil {
94 +
		out, err := mapstructure.DecodeHookExec(e.config.EncodeHook, value, value)
95 +
		if err != nil {
96 +
			return nil, fmt.Errorf("error running encode hook: %w", err)
97 +
		}
98 +
		return out, nil
99 +
	}
100 +
	return value.Interface(), nil
101 +
}
102 +
103 +
// encodeStruct encodes the struct by iterating over the fields, getting the
104 +
// mapstructure tagInfo for each exported field, and encoding the value.
105 +
func (e *Encoder) encodeStruct(value reflect.Value) (interface{}, error) {
106 +
	if value.Kind() != reflect.Struct {
107 +
		return nil, &reflect.ValueError{
108 +
			Method: "encodeStruct",
109 +
			Kind:   value.Kind(),
110 +
		}
111 +
	}
112 +
	out, err := e.encodeHook(value)
113 +
	if err != nil {
114 +
		return nil, err
115 +
	}
116 +
	value = reflect.ValueOf(out)
117 +
	// if the output of encodeHook is no longer a struct,
118 +
	// call encode against it.
119 +
	if value.Kind() != reflect.Struct {
120 +
		return e.encode(value)
121 +
	}
122 +
	result := make(map[string]interface{})
123 +
	for i := 0; i < value.NumField(); i++ {
124 +
		field := value.Field(i)
125 +
		if field.CanInterface() {
126 +
			info := getTagInfo(value.Type().Field(i))
127 +
			if (info.omitEmpty && field.IsZero()) || info.name == optionSkip {
128 +
				continue
129 +
			}
130 +
			encoded, err := e.encode(field)
131 +
			if err != nil {
132 +
				return nil, fmt.Errorf("error encoding field %q: %w", info.name, err)
133 +
			}
134 +
			if info.squash {
135 +
				if m, ok := encoded.(map[string]interface{}); ok {
136 +
					for k, v := range m {
137 +
						result[k] = v
138 +
					}
139 +
				}
140 +
			} else {
141 +
				result[info.name] = encoded
142 +
			}
143 +
		}
144 +
	}
145 +
	return result, nil
146 +
}
147 +
148 +
// encodeSlice iterates over the slice and encodes each of the elements.
149 +
func (e *Encoder) encodeSlice(value reflect.Value) (interface{}, error) {
150 +
	if value.Kind() != reflect.Slice {
151 +
		return nil, &reflect.ValueError{
152 +
			Method: "encodeSlice",
153 +
			Kind:   value.Kind(),
154 +
		}
155 +
	}
156 +
	result := make([]interface{}, value.Len())
157 +
	for i := 0; i < value.Len(); i++ {
158 +
		var err error
159 +
		if result[i], err = e.encode(value.Index(i)); err != nil {
160 +
			return nil, fmt.Errorf("error encoding element in slice at index %d: %w", i, err)
161 +
		}
162 +
	}
163 +
	return result, nil
164 +
}
165 +
166 +
// encodeMap encodes a map by encoding the key and value. Returns errNonStringEncodedKey
167 +
// if the key is not encoded into a string.
168 +
func (e *Encoder) encodeMap(value reflect.Value) (interface{}, error) {
169 +
	if value.Kind() != reflect.Map {
170 +
		return nil, &reflect.ValueError{
171 +
			Method: "encodeMap",
172 +
			Kind:   value.Kind(),
173 +
		}
174 +
	}
175 +
	result := make(map[string]interface{})
176 +
	iterator := value.MapRange()
177 +
	for iterator.Next() {
178 +
		encoded, err := e.encode(iterator.Key())
179 +
		if err != nil {
180 +
			return nil, fmt.Errorf("error encoding key: %w", err)
181 +
		}
182 +
		key, ok := encoded.(string)
183 +
		if !ok {
184 +
			return nil, fmt.Errorf("%w key %q, kind: %v", errNonStringEncodedKey, iterator.Key().Interface(), iterator.Key().Kind())
185 +
		}
186 +
		if _, ok = result[key]; ok {
187 +
			return nil, fmt.Errorf("duplicate key %q while encoding", key)
188 +
		}
189 +
		if result[key], err = e.encode(iterator.Value()); err != nil {
190 +
			return nil, fmt.Errorf("error encoding map value for key %q: %w", key, err)
191 +
		}
192 +
	}
193 +
	return result, nil
194 +
}
195 +
196 +
// getTagInfo looks up the mapstructure tag and uses that if available.
197 +
// Uses the lowercase field if not found. Checks for omitempty and squash.
198 +
func getTagInfo(field reflect.StructField) *tagInfo {
199 +
	info := tagInfo{}
200 +
	if tag, ok := field.Tag.Lookup(tagNameMapStructure); ok {
201 +
		options := strings.Split(tag, optionSeparator)
202 +
		info.name = options[0]
203 +
		if len(options) > 1 {
204 +
			for _, option := range options[1:] {
205 +
				switch option {
206 +
				case optionOmitEmpty:
207 +
					info.omitEmpty = true
208 +
				case optionSquash, optionRemain:
209 +
					info.squash = true
210 +
				}
211 +
			}
212 +
		}
213 +
	} else {
214 +
		info.name = strings.ToLower(field.Name)
215 +
	}
216 +
	return &info
217 +
}
218 +
219 +
// TextMarshalerHookFunc returns a DecodeHookFuncValue that checks
220 +
// for the encoding.TextMarshaler interface and calls the MarshalText
221 +
// function if found.
222 +
func TextMarshalerHookFunc() mapstructure.DecodeHookFuncValue {
223 +
	return func(from reflect.Value, _ reflect.Value) (interface{}, error) {
224 +
		marshaler, ok := from.Interface().(encoding.TextMarshaler)
225 +
		if !ok {
226 +
			return from.Interface(), nil
227 +
		}
228 +
		out, err := marshaler.MarshalText()
229 +
		if err != nil {
230 +
			return nil, err
231 +
		}
232 +
		return string(out), nil
233 +
	}
234 +
}

@@ -23,6 +23,8 @@
Loading
23 23
	"github.com/knadh/koanf/maps"
24 24
	"github.com/knadh/koanf/providers/confmap"
25 25
	"github.com/mitchellh/mapstructure"
26 +
27 +
	encoder "go.opentelemetry.io/collector/confmap/internal/mapstructure"
26 28
)
27 29
28 30
const (
@@ -66,6 +68,20 @@
Loading
66 68
	return decodeConfig(l, result, true)
67 69
}
68 70
71 +
// Marshal encodes the config and merges it into the Conf.
72 +
func (l *Conf) Marshal(rawVal interface{}) error {
73 +
	enc := encoder.New(encoderConfig(rawVal))
74 +
	data, err := enc.Encode(rawVal)
75 +
	if err != nil {
76 +
		return err
77 +
	}
78 +
	out, ok := data.(map[string]interface{})
79 +
	if !ok {
80 +
		return fmt.Errorf("invalid config encoding")
81 +
	}
82 +
	return l.Merge(NewFromStringMap(out))
83 +
}
84 +
69 85
// Get can retrieve any value given the key to use.
70 86
func (l *Conf) Get(key string) interface{} {
71 87
	return l.k.Get(key)
@@ -133,6 +149,18 @@
Loading
133 149
	return decoder.Decode(m.ToStringMap())
134 150
}
135 151
152 +
// encoderConfig returns a default encoder.EncoderConfig that includes
153 +
// an EncodeHook that handles both TextMarshaller and Marshaler
154 +
// interfaces.
155 +
func encoderConfig(rawVal interface{}) *encoder.EncoderConfig {
156 +
	return &encoder.EncoderConfig{
157 +
		EncodeHook: mapstructure.ComposeDecodeHookFunc(
158 +
			encoder.TextMarshalerHookFunc(),
159 +
			marshalerHookFunc(rawVal),
160 +
		),
161 +
	}
162 +
}
163 +
136 164
// In cases where a config has a mapping of something to a struct pointers
137 165
// we want nil values to resolve to a pointer to the zero value of the
138 166
// underlying struct just as we want nil values of a mapping of something
@@ -239,9 +267,43 @@
Loading
239 267
	}
240 268
}
241 269
270 +
// marshalerHookFunc returns a DecodeHookFuncValue that checks structs that aren't
271 +
// the original to see if they implement the Marshaler interface.
272 +
func marshalerHookFunc(orig interface{}) mapstructure.DecodeHookFuncValue {
273 +
	origType := reflect.TypeOf(orig)
274 +
	return func(from reflect.Value, _ reflect.Value) (interface{}, error) {
275 +
		if from.Kind() != reflect.Struct {
276 +
			return from.Interface(), nil
277 +
		}
278 +
279 +
		// ignore original to avoid infinite loop.
280 +
		if from.Type() == origType && reflect.DeepEqual(from.Interface(), orig) {
281 +
			return from.Interface(), nil
282 +
		}
283 +
		marshaler, ok := from.Interface().(Marshaler)
284 +
		if !ok {
285 +
			return from.Interface(), nil
286 +
		}
287 +
		conf := New()
288 +
		if err := marshaler.Marshal(conf); err != nil {
289 +
			return nil, err
290 +
		}
291 +
		return conf.ToStringMap(), nil
292 +
	}
293 +
}
294 +
242 295
// Unmarshaler interface may be implemented by types to customize their behavior when being unmarshaled from a Conf.
243 296
type Unmarshaler interface {
244 297
	// Unmarshal a Conf into the struct in a custom way.
245 298
	// The Conf for this specific component may be nil or empty if no config available.
246 299
	Unmarshal(component *Conf) error
247 300
}
301 +
302 +
// Marshaler defines an optional interface for custom configuration marshaling.
303 +
// A configuration struct can implement this interface to override the default
304 +
// marshaling.
305 +
type Marshaler interface {
306 +
	// Marshal the config into a Conf in a custom way.
307 +
	// The Conf will be empty and can be merged into.
308 +
	Marshal(component *Conf) error
309 +
}

@@ -41,6 +41,7 @@
Loading
41 41
// that every component should generate.
42 42
type Level int32
43 43
44 +
var _ encoding.TextMarshaler = (*Level)(nil)
44 45
var _ encoding.TextUnmarshaler = (*Level)(nil)
45 46
46 47
func (l Level) String() string {
@@ -57,6 +58,11 @@
Loading
57 58
	return "unknown"
58 59
}
59 60
61 +
// MarshalText marshals Level to text.
62 +
func (l Level) MarshalText() (text []byte, err error) {
63 +
	return []byte(l.String()), nil
64 +
}
65 +
60 66
// UnmarshalText unmarshalls text to a Level.
61 67
func (l *Level) UnmarshalText(text []byte) error {
62 68
	if l == nil {

@@ -69,6 +69,12 @@
Loading
69 69
	return id.nameVal
70 70
}
71 71
72 +
// MarshalText implements the encoding.TextMarshaler interface.
73 +
// This marshals the type and name as one string in the config.
74 +
func (id ComponentID) MarshalText() (text []byte, err error) {
75 +
	return []byte(id.String()), nil
76 +
}
77 +
72 78
// UnmarshalText implements the encoding.TextUnmarshaler interface.
73 79
func (id *ComponentID) UnmarshalText(text []byte) error {
74 80
	idStr := string(text)
Files Coverage
cmd 55.60%
component 95.51%
config 93.48%
confmap 97.19%
consumer 100.00%
exporter 90.38%
extension 84.17%
featuregate 100.00%
internal 88.38%
obsreport 97.26%
pdata 94.82%
processor 94.01%
receiver 84.43%
service 85.71%
client/client.go 100.00%
Project Totals (219 files) 91.87%

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading