Adding upstream version 0.8.9.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
3b2c48b5e4
commit
c0c4addb85
285 changed files with 25880 additions and 0 deletions
509
pkg/format/formatter.go
Normal file
509
pkg/format/formatter.go
Normal file
|
@ -0,0 +1,509 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/util"
|
||||
)
|
||||
|
||||
// Constants for map parsing and type sizes.
|
||||
const (
|
||||
KeyValuePairSize = 2 // Number of elements in a key:value pair
|
||||
Int32BitSize = 32 // Bit size for 32-bit integers
|
||||
Int64BitSize = 64 // Bit size for 64-bit integers
|
||||
)
|
||||
|
||||
// Errors defined as static variables for better error handling.
|
||||
var (
|
||||
ErrInvalidEnumValue = errors.New("not a valid enum value")
|
||||
ErrInvalidBoolValue = errors.New("accepted values are 1, true, yes or 0, false, no")
|
||||
ErrUnsupportedFieldKey = errors.New("field key format is not supported")
|
||||
ErrInvalidFieldValue = errors.New("invalid field value format")
|
||||
ErrUnsupportedField = errors.New("field format is not supported")
|
||||
ErrInvalidFieldCount = errors.New("invalid field value count")
|
||||
ErrInvalidFieldKind = errors.New("invalid field kind")
|
||||
ErrUnsupportedMapValue = errors.New("map value format is not supported")
|
||||
ErrInvalidFieldValueData = errors.New("invalid field value")
|
||||
ErrFailedToSetEnumValue = errors.New("failed to set enum value")
|
||||
ErrUnexpectedUintKind = errors.New("unexpected uint kind")
|
||||
ErrUnexpectedIntKind = errors.New("unexpected int kind")
|
||||
ErrParseIntFailed = errors.New("failed to parse integer")
|
||||
ErrParseUintFailed = errors.New("failed to parse unsigned integer")
|
||||
)
|
||||
|
||||
// GetServiceConfig extracts the inner config from a service.
|
||||
func GetServiceConfig(service types.Service) types.ServiceConfig {
|
||||
serviceValue := reflect.Indirect(reflect.ValueOf(service))
|
||||
|
||||
configField, ok := serviceValue.Type().FieldByName("Config")
|
||||
if !ok {
|
||||
panic("service does not have a Config field")
|
||||
}
|
||||
|
||||
configRef := serviceValue.FieldByIndex(configField.Index)
|
||||
if configRef.IsNil() {
|
||||
configType := configField.Type
|
||||
if configType.Kind() == reflect.Ptr {
|
||||
configType = configType.Elem()
|
||||
}
|
||||
|
||||
newConfig := reflect.New(configType).Interface()
|
||||
if config, ok := newConfig.(types.ServiceConfig); ok {
|
||||
return config
|
||||
}
|
||||
|
||||
panic("failed to create new config instance")
|
||||
}
|
||||
|
||||
if config, ok := configRef.Interface().(types.ServiceConfig); ok {
|
||||
return config
|
||||
}
|
||||
|
||||
panic("config reference is not a ServiceConfig")
|
||||
}
|
||||
|
||||
// ColorFormatTree generates a color-highlighted string representation of a node tree.
|
||||
func ColorFormatTree(rootNode *ContainerNode, withValues bool) string {
|
||||
return ConsoleTreeRenderer{WithValues: withValues}.RenderTree(rootNode, "")
|
||||
}
|
||||
|
||||
// GetServiceConfigFormat retrieves type and field information from a service's config.
|
||||
func GetServiceConfigFormat(service types.Service) *ContainerNode {
|
||||
return GetConfigFormat(GetServiceConfig(service))
|
||||
}
|
||||
|
||||
// GetConfigFormat retrieves type and field information from a ServiceConfig.
|
||||
func GetConfigFormat(config types.ServiceConfig) *ContainerNode {
|
||||
return getRootNode(config)
|
||||
}
|
||||
|
||||
// SetConfigField updates a config field with a deserialized value from a string.
|
||||
func SetConfigField(config reflect.Value, field FieldInfo, inputValue string) (bool, error) {
|
||||
configField := config.FieldByName(field.Name)
|
||||
if field.EnumFormatter != nil {
|
||||
return setEnumField(configField, field, inputValue)
|
||||
}
|
||||
|
||||
switch field.Type.Kind() {
|
||||
case reflect.String:
|
||||
configField.SetString(inputValue)
|
||||
|
||||
return true, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return setIntField(configField, field, inputValue)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return setUintField(configField, field, inputValue)
|
||||
case reflect.Bool:
|
||||
return setBoolField(configField, inputValue)
|
||||
case reflect.Map:
|
||||
return setMapField(configField, field, inputValue)
|
||||
case reflect.Struct:
|
||||
return setStructField(configField, field, inputValue)
|
||||
case reflect.Slice, reflect.Array:
|
||||
return setSliceOrArrayField(configField, field, inputValue)
|
||||
case reflect.Invalid,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Complex64,
|
||||
reflect.Complex128,
|
||||
reflect.Chan,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Pointer,
|
||||
reflect.UnsafePointer:
|
||||
return false, fmt.Errorf("%w: %v", ErrInvalidFieldKind, field.Type.Kind())
|
||||
default:
|
||||
return false, fmt.Errorf("%w: %v", ErrInvalidFieldKind, field.Type.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
// setIntField handles integer field setting.
|
||||
func setIntField(configField reflect.Value, field FieldInfo, inputValue string) (bool, error) {
|
||||
number, base := util.StripNumberPrefix(inputValue)
|
||||
|
||||
value, err := strconv.ParseInt(number, base, field.Type.Bits())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: %w", ErrParseIntFailed, err)
|
||||
}
|
||||
|
||||
configField.SetInt(value)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// setUintField handles unsigned integer field setting.
|
||||
func setUintField(configField reflect.Value, field FieldInfo, inputValue string) (bool, error) {
|
||||
number, base := util.StripNumberPrefix(inputValue)
|
||||
|
||||
value, err := strconv.ParseUint(number, base, field.Type.Bits())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%w: %w", ErrParseUintFailed, err)
|
||||
}
|
||||
|
||||
configField.SetUint(value)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// setBoolField handles boolean field setting.
|
||||
func setBoolField(configField reflect.Value, inputValue string) (bool, error) {
|
||||
value, ok := ParseBool(inputValue, false)
|
||||
if !ok {
|
||||
return false, ErrInvalidBoolValue
|
||||
}
|
||||
|
||||
configField.SetBool(value)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// setMapField handles map field setting.
|
||||
func setMapField(configField reflect.Value, field FieldInfo, inputValue string) (bool, error) {
|
||||
if field.Type.Key().Kind() != reflect.String {
|
||||
return false, ErrUnsupportedFieldKey
|
||||
}
|
||||
|
||||
mapValue := reflect.MakeMap(field.Type)
|
||||
|
||||
pairs := strings.Split(inputValue, ",")
|
||||
for _, pair := range pairs {
|
||||
elems := strings.Split(pair, ":")
|
||||
if len(elems) != KeyValuePairSize {
|
||||
return false, ErrInvalidFieldValue
|
||||
}
|
||||
|
||||
key, valueRaw := elems[0], elems[1]
|
||||
|
||||
value, err := getMapValue(field.Type.Elem(), valueRaw)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
mapValue.SetMapIndex(reflect.ValueOf(key), value)
|
||||
}
|
||||
|
||||
configField.Set(mapValue)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// setStructField handles struct field setting.
|
||||
func setStructField(configField reflect.Value, field FieldInfo, inputValue string) (bool, error) {
|
||||
valuePtr, err := GetConfigPropFromString(field.Type, inputValue)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
configField.Set(valuePtr.Elem())
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// setSliceOrArrayField handles slice or array field setting.
|
||||
func setSliceOrArrayField(
|
||||
configField reflect.Value,
|
||||
field FieldInfo,
|
||||
inputValue string,
|
||||
) (bool, error) {
|
||||
elemType := field.Type.Elem()
|
||||
elemKind := elemType.Kind()
|
||||
|
||||
if elemKind == reflect.Ptr {
|
||||
elemKind = elemType.Elem().Kind()
|
||||
}
|
||||
|
||||
if elemKind != reflect.Struct && elemKind != reflect.String {
|
||||
return false, ErrUnsupportedField
|
||||
}
|
||||
|
||||
values := strings.Split(inputValue, string(field.ItemSeparator))
|
||||
if field.Type.Kind() == reflect.Array && len(values) != field.Type.Len() {
|
||||
return false, fmt.Errorf("%w: needs to be %d", ErrInvalidFieldCount, field.Type.Len())
|
||||
}
|
||||
|
||||
return setSliceOrArrayValues(configField, field, elemType, values)
|
||||
}
|
||||
|
||||
// setSliceOrArrayValues sets the actual values for slice or array fields.
|
||||
func setSliceOrArrayValues(
|
||||
configField reflect.Value,
|
||||
field FieldInfo,
|
||||
elemType reflect.Type,
|
||||
values []string,
|
||||
) (bool, error) {
|
||||
isPtrSlice := elemType.Kind() == reflect.Ptr
|
||||
baseType := elemType
|
||||
|
||||
if isPtrSlice {
|
||||
baseType = elemType.Elem()
|
||||
}
|
||||
|
||||
if baseType.Kind() == reflect.Struct {
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(elemType), 0, len(values))
|
||||
|
||||
for _, v := range values {
|
||||
propPtr, err := GetConfigPropFromString(baseType, v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if isPtrSlice {
|
||||
slice = reflect.Append(slice, propPtr)
|
||||
} else {
|
||||
slice = reflect.Append(slice, propPtr.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
configField.Set(slice)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Handle string slice/array
|
||||
value := reflect.ValueOf(values)
|
||||
|
||||
if field.Type.Kind() == reflect.Array {
|
||||
arr := reflect.Indirect(reflect.New(field.Type))
|
||||
reflect.Copy(arr, value)
|
||||
configField.Set(arr)
|
||||
} else {
|
||||
configField.Set(value)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// setEnumField handles enum field setting.
|
||||
func setEnumField(configField reflect.Value, field FieldInfo, inputValue string) (bool, error) {
|
||||
value := field.EnumFormatter.Parse(inputValue)
|
||||
if value == EnumInvalid {
|
||||
return false, fmt.Errorf(
|
||||
"%w: accepted values are %v",
|
||||
ErrInvalidEnumValue,
|
||||
field.EnumFormatter.Names(),
|
||||
)
|
||||
}
|
||||
|
||||
configField.SetInt(int64(value))
|
||||
|
||||
if actual := int(configField.Int()); actual != value {
|
||||
return false, fmt.Errorf(
|
||||
"%w: expected %d, got %d (canSet: %v)",
|
||||
ErrFailedToSetEnumValue,
|
||||
value,
|
||||
actual,
|
||||
configField.CanSet(),
|
||||
)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// getMapValue converts a raw string to a map value based on type.
|
||||
func getMapValue(valueType reflect.Type, valueRaw string) (reflect.Value, error) {
|
||||
switch valueType.Kind() {
|
||||
case reflect.String:
|
||||
return reflect.ValueOf(valueRaw), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return getMapUintValue(valueRaw, valueType)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return getMapIntValue(valueRaw, valueType)
|
||||
case reflect.Invalid,
|
||||
reflect.Bool,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Complex64,
|
||||
reflect.Complex128,
|
||||
reflect.Array,
|
||||
reflect.Chan,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Map,
|
||||
reflect.Pointer,
|
||||
reflect.Slice,
|
||||
reflect.Struct,
|
||||
reflect.UnsafePointer:
|
||||
return reflect.Value{}, ErrUnsupportedMapValue
|
||||
default:
|
||||
return reflect.Value{}, ErrUnsupportedMapValue
|
||||
}
|
||||
}
|
||||
|
||||
// getMapUintValue converts a string to an unsigned integer map value.
|
||||
func getMapUintValue(valueRaw string, valueType reflect.Type) (reflect.Value, error) {
|
||||
number, base := util.StripNumberPrefix(valueRaw)
|
||||
|
||||
numValue, err := strconv.ParseUint(number, base, valueType.Bits())
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("%w: %w", ErrParseUintFailed, err)
|
||||
}
|
||||
|
||||
switch valueType.Kind() {
|
||||
case reflect.Uint:
|
||||
return reflect.ValueOf(uint(numValue)), nil
|
||||
case reflect.Uint8:
|
||||
if numValue > math.MaxUint8 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds uint8 range",
|
||||
ErrParseUintFailed,
|
||||
numValue,
|
||||
)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint8(numValue)), nil
|
||||
case reflect.Uint16:
|
||||
if numValue > math.MaxUint16 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds uint16 range",
|
||||
ErrParseUintFailed,
|
||||
numValue,
|
||||
)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint16(numValue)), nil
|
||||
case reflect.Uint32:
|
||||
if numValue > math.MaxUint32 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds uint32 range",
|
||||
ErrParseUintFailed,
|
||||
numValue,
|
||||
)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(uint32(numValue)), nil
|
||||
case reflect.Uint64:
|
||||
return reflect.ValueOf(numValue), nil
|
||||
case reflect.Invalid,
|
||||
reflect.Bool,
|
||||
reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Complex64,
|
||||
reflect.Complex128,
|
||||
reflect.Array,
|
||||
reflect.Chan,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Map,
|
||||
reflect.Pointer,
|
||||
reflect.Slice,
|
||||
reflect.String,
|
||||
reflect.Struct,
|
||||
reflect.UnsafePointer:
|
||||
return reflect.Value{}, ErrUnexpectedUintKind
|
||||
default:
|
||||
return reflect.Value{}, ErrUnexpectedUintKind
|
||||
}
|
||||
}
|
||||
|
||||
// getMapIntValue converts a string to a signed integer map value.
|
||||
func getMapIntValue(valueRaw string, valueType reflect.Type) (reflect.Value, error) {
|
||||
number, base := util.StripNumberPrefix(valueRaw)
|
||||
|
||||
numValue, err := strconv.ParseInt(number, base, valueType.Bits())
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("%w: %w", ErrParseIntFailed, err)
|
||||
}
|
||||
|
||||
switch valueType.Kind() {
|
||||
case reflect.Int:
|
||||
bits := valueType.Bits()
|
||||
if bits == Int32BitSize {
|
||||
if numValue < math.MinInt32 || numValue > math.MaxInt32 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds int%d range",
|
||||
ErrParseIntFailed,
|
||||
numValue,
|
||||
bits,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int(numValue)), nil
|
||||
case reflect.Int8:
|
||||
if numValue < math.MinInt8 || numValue > math.MaxInt8 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds int8 range",
|
||||
ErrParseIntFailed,
|
||||
numValue,
|
||||
)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int8(numValue)), nil
|
||||
case reflect.Int16:
|
||||
if numValue < math.MinInt16 || numValue > math.MaxInt16 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds int16 range",
|
||||
ErrParseIntFailed,
|
||||
numValue,
|
||||
)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int16(numValue)), nil
|
||||
case reflect.Int32:
|
||||
if numValue < math.MinInt32 || numValue > math.MaxInt32 {
|
||||
return reflect.Value{}, fmt.Errorf(
|
||||
"%w: value %d exceeds int32 range",
|
||||
ErrParseIntFailed,
|
||||
numValue,
|
||||
)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(int32(numValue)), nil
|
||||
case reflect.Int64:
|
||||
return reflect.ValueOf(numValue), nil
|
||||
case reflect.Invalid,
|
||||
reflect.Bool,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Uintptr,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Complex64,
|
||||
reflect.Complex128,
|
||||
reflect.Array,
|
||||
reflect.Chan,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Map,
|
||||
reflect.Pointer,
|
||||
reflect.Slice,
|
||||
reflect.String,
|
||||
reflect.Struct,
|
||||
reflect.UnsafePointer:
|
||||
return reflect.Value{}, ErrUnexpectedIntKind
|
||||
default:
|
||||
return reflect.Value{}, ErrUnexpectedIntKind
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfigFieldString converts a config field value to its string representation.
|
||||
func GetConfigFieldString(config reflect.Value, field FieldInfo) (string, error) {
|
||||
configField := config.FieldByName(field.Name)
|
||||
if field.IsEnum() {
|
||||
return field.EnumFormatter.Print(int(configField.Int())), nil
|
||||
}
|
||||
|
||||
strVal, token := getValueNodeValue(configField, &field)
|
||||
if token == ErrorToken {
|
||||
return "", ErrInvalidFieldValueData
|
||||
}
|
||||
|
||||
return strVal, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue