192 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright The OpenTelemetry Authors
 | |
| // SPDX-License-Identifier: Apache-2.0
 | |
| 
 | |
| package configoptional // import "go.opentelemetry.io/collector/config/configoptional"
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 
 | |
| 	"go.opentelemetry.io/collector/confmap"
 | |
| )
 | |
| 
 | |
| type flavor int
 | |
| 
 | |
| const (
 | |
| 	noneFlavor    flavor = 0
 | |
| 	defaultFlavor flavor = 1
 | |
| 	someFlavor    flavor = 2
 | |
| )
 | |
| 
 | |
| // Optional represents a value that may or may not be present.
 | |
| // It supports two flavors for all types: Some(value) and None.
 | |
| // It supports a third flavor for struct types: Default(defaultVal).
 | |
| //
 | |
| // For struct types, it supports unmarshaling from a configuration source.
 | |
| // The zero value of Optional is None.
 | |
| type Optional[T any] struct {
 | |
| 	// value is the value of the Optional.
 | |
| 	value T
 | |
| 
 | |
| 	// flavor indicates the flavor of the Optional.
 | |
| 	// The zero value of flavor is noneFlavor.
 | |
| 	flavor flavor
 | |
| }
 | |
| 
 | |
| // deref a reflect.Type to its underlying type.
 | |
| func deref(t reflect.Type) reflect.Type {
 | |
| 	for t.Kind() == reflect.Ptr {
 | |
| 		t = t.Elem()
 | |
| 	}
 | |
| 	return t
 | |
| }
 | |
| 
 | |
| // assertStructKind checks if T can be dereferenced into a type with struct kind.
 | |
| //
 | |
| // We assert this because our unmarshaling logic currently only supports structs.
 | |
| // This can be removed if we ever support scalar values.
 | |
| func assertStructKind[T any]() error {
 | |
| 	var instance T
 | |
| 	t := deref(reflect.TypeOf(instance))
 | |
| 	if t.Kind() != reflect.Struct {
 | |
| 		return fmt.Errorf("configoptional: %q does not have a struct kind", t)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // assertNoEnabledField checks that a struct type
 | |
| // does not have a field with a mapstructure tag "enabled".
 | |
| //
 | |
| // We assert this because we discussed an alternative design where we have an explicit
 | |
| // "enabled" field in the struct to indicate if the struct is enabled or not.
 | |
| // See https://github.com/open-telemetry/opentelemetry-collector/pull/13060.
 | |
| // This can be removed if we ever support such a design (or if we just want to allow
 | |
| // the "enabled" field in the struct).
 | |
| func assertNoEnabledField[T any]() error {
 | |
| 	var i T
 | |
| 	t := deref(reflect.TypeOf(i))
 | |
| 	if t.Kind() != reflect.Struct {
 | |
| 		// Not a struct, no need to check for "enabled" field.
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Check if the struct has a field with the name "enabled".
 | |
| 	for i := 0; i < t.NumField(); i++ {
 | |
| 		field := t.Field(i)
 | |
| 		mapstructureTags := strings.SplitN(field.Tag.Get("mapstructure"), ",", 2)
 | |
| 		if len(mapstructureTags) > 0 && mapstructureTags[0] == "enabled" {
 | |
| 			return errors.New("configoptional: underlying type cannot have a field with mapstructure tag 'enabled'")
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Some creates an Optional with a value and no factory.
 | |
| //
 | |
| // It panics if T has a field with the mapstructure tag "enabled".
 | |
| func Some[T any](value T) Optional[T] {
 | |
| 	if err := assertNoEnabledField[T](); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	return Optional[T]{value: value, flavor: someFlavor}
 | |
| }
 | |
| 
 | |
| // Default creates an Optional with a default value for unmarshaling.
 | |
| //
 | |
| // It panics if
 | |
| // - T is not a struct OR
 | |
| // - T has a field with the mapstructure tag "enabled".
 | |
| func Default[T any](value T) Optional[T] {
 | |
| 	err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	return Optional[T]{value: value, flavor: defaultFlavor}
 | |
| }
 | |
| 
 | |
| // None has no value. It has the same behavior as a nil pointer when unmarshaling.
 | |
| //
 | |
| // The zero value of Optional[T] is None[T]. Prefer using this constructor
 | |
| // for validation.
 | |
| //
 | |
| // It panics if T has a field with the mapstructure tag "enabled".
 | |
| func None[T any]() Optional[T] {
 | |
| 	if err := assertNoEnabledField[T](); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	return Optional[T]{}
 | |
| }
 | |
| 
 | |
| // HasValue checks if the Optional has a value.
 | |
| func (o Optional[T]) HasValue() bool {
 | |
| 	return o.flavor == someFlavor
 | |
| }
 | |
| 
 | |
| // Get returns the value of the Optional.
 | |
| // If the value is not present, it returns nil.
 | |
| func (o *Optional[T]) Get() *T {
 | |
| 	if !o.HasValue() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return &o.value
 | |
| }
 | |
| 
 | |
| var _ confmap.Unmarshaler = (*Optional[any])(nil)
 | |
| 
 | |
| // Unmarshal the configuration into the Optional value.
 | |
| //
 | |
| // The behavior of this method depends on the state of the Optional:
 | |
| //   - None[T]: does nothing if the configuration is nil, otherwise it unmarshals into the zero value of T.
 | |
| //   - Some[T](val): equivalent to unmarshaling into a field of type T with value val.
 | |
| //   - Default[T](val), equivalent to unmarshaling into a field of type T with base value val,
 | |
| //     using val without overrides from the configuration if the configuration is nil.
 | |
| //
 | |
| // T must be derefenceable to a type with struct kind and not have an 'enabled' field.
 | |
| // Scalar values are not supported.
 | |
| func (o *Optional[T]) Unmarshal(conf *confmap.Conf) error {
 | |
| 	if err := assertNoEnabledField[T](); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if o.flavor == noneFlavor && conf.ToStringMap() == nil {
 | |
| 		// If the Optional is None and the configuration is nil, we do nothing.
 | |
| 		// This replicates the behavior of unmarshaling into a field with a nil pointer.
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if err := conf.Unmarshal(&o.value); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	o.flavor = someFlavor
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var _ confmap.Marshaler = (*Optional[any])(nil)
 | |
| 
 | |
| // Marshal the Optional value into the configuration.
 | |
| // If the Optional is None or Default, it does not marshal anything.
 | |
| // If the Optional is Some, it marshals the value into the configuration.
 | |
| //
 | |
| // T must be derefenceable to a type with struct kind.
 | |
| // Scalar values are not supported.
 | |
| func (o Optional[T]) Marshal(conf *confmap.Conf) error {
 | |
| 	if err := assertStructKind[T](); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if o.flavor == noneFlavor || o.flavor == defaultFlavor {
 | |
| 		// Optional is None or Default, do not marshal anything.
 | |
| 		return conf.Marshal(map[string]any(nil))
 | |
| 	}
 | |
| 
 | |
| 	if err := conf.Marshal(o.value); err != nil {
 | |
| 		return fmt.Errorf("configoptional: failed to marshal Optional value: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |