Files
aac1a8f39c [confmap] Stabilize confmap validation facilities (#15142)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

The `xconfmap.Validator` interface has existed in some form for a very
long time now and is ready to be stabilized. Similarly, the
`xconfmap.Validate` method has existed in it's current state for over a
year and doesn't have any open issues I'm aware of. It should also be
ready for stabilization.

Many modules depend on `xconfmap` merely for the validation facilities
(as can be seen by the go.mod diffs), so this will allow them to depend
on only stable modules.

---------

Co-authored-by: Alex Boten <[email protected]>
2026-05-08 13:33:15 +00:00

116 lines
3.0 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configopaque // import "go.opentelemetry.io/collector/config/configopaque"
import (
"cmp"
"fmt"
"iter"
"slices"
"go.opentelemetry.io/collector/confmap"
)
// Pair is an element of a MapList, and consists of a name and an opaque value.
type Pair struct {
Name string `mapstructure:"name"`
Value String `mapstructure:"value"`
// prevent unkeyed literal initialization
_ struct{}
}
// MapList is a replacement for map[string]configopaque.String with a similar API,
// which can also be unmarshalled from (and is stored as) a list of name/value pairs.
//
// Pairs are assumed to have distinct names. This is checked during config validation.
type MapList []Pair
var _ confmap.Unmarshaler = (*MapList)(nil)
// Unmarshal is called by the Collector when unmarshalling from a map.
// When the input config is a slice, this will be skipped,
// and mapstructure's default unmarshalling logic will be used.
func (ml *MapList) Unmarshal(conf *confmap.Conf) error {
var m2 map[string]String
if err := conf.Unmarshal(&m2); err != nil {
return err
}
*ml = make(MapList, 0, len(m2))
for name, value := range m2 {
*ml = append(*ml, Pair{
Name: name,
Value: value,
})
}
slices.SortFunc(*ml, func(p1, p2 Pair) int {
return cmp.Compare(p1.Name, p2.Name)
})
return nil
}
var _ confmap.Validator = MapList(nil)
func (ml MapList) Validate() error {
// Check for duplicate keys
counts := make(map[string]int, len(ml))
for _, OpaquePair := range ml {
counts[OpaquePair.Name]++
}
if len(counts) == len(ml) {
return nil
}
var duplicates []string
for name, cnt := range counts {
if cnt > 1 {
duplicates = append(duplicates, name)
}
}
slices.Sort(duplicates)
return fmt.Errorf("duplicate keys in map-style list: %v", duplicates)
}
var _ iter.Seq2[string, String] = MapList(nil).Iter
// Iter is an iterator over key/value pairs for use in for-range loops.
// It is the MapList equivalent of directly ranging over a map.
func (ml MapList) Iter(yield func(name string, value String) bool) {
for _, OpaquePair := range ml {
if !yield(OpaquePair.Name, OpaquePair.Value) {
break
}
}
}
// Get looks up a pair's value based on its name.
// It is the MapList equivalent of `val, ok := m[key]`.
// However, it has linear time complexity.
func (ml MapList) Get(name string) (val String, ok bool) {
for _, OpaquePair := range ml {
if OpaquePair.Name == name {
return OpaquePair.Value, true
}
}
return val, false
}
// Set sets the value corresponding to a given name.
// It is the MapList equivalent of `m[key] = val`.
// However, it has linear time complexity,
// and does not affect shallow copies.
func (ml *MapList) Set(name string, val String) {
if ml == nil {
panic("assignment to entry in nil *MapList")
}
for i, OpaquePair := range *ml {
if OpaquePair.Name == name {
*ml = slices.Clone(*ml)
(*ml)[i].Value = val
return
}
}
*ml = append(make(MapList, 0, len(*ml)+1), *ml...)
*ml = append(*ml, Pair{Name: name, Value: val})
}