mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
Some checks are pending
CodeQL / codeql (push) Waiting to run
Docker Check / lint (push) Waiting to run
Docker Check / build (push) Waiting to run
Gateway Conformance / gateway-conformance (push) Waiting to run
Gateway Conformance / gateway-conformance-libp2p-experiment (push) Waiting to run
Go Build / go-build (push) Waiting to run
Go Check / go-check (push) Waiting to run
Go Lint / go-lint (push) Waiting to run
Go Test / go-test (push) Waiting to run
Interop / interop-prep (push) Waiting to run
Interop / helia-interop (push) Blocked by required conditions
Interop / ipfs-webui (push) Blocked by required conditions
Sharness / sharness-test (push) Waiting to run
Spell Check / spellcheck (push) Waiting to run
* refactor: consolidate Provider/Reprovider into unified Provide config - merge Provider and Reprovider configs into single Provide section - add fs-repo-17-to-18 migration for config consolidation - improve migration ergonomics with common package utilities - convert deprecated "flat" strategy to "all" during migration - improve Provide docs * docs: add total_provide_count metric guidance - document how to monitor provide success rates via prometheus metrics - add performance comparison section to changelog - explain how to evaluate sweep vs legacy provider effectiveness * fix: add OpenTelemetry meter provider for metrics - set up meter provider with Prometheus exporter in daemon - enables metrics from external libs like go-libp2p-kad-dht - fixes missing total_provide_count_total when SweepEnabled=true - update docs to reflect actual metric names --------- Co-authored-by: gammazero <11790789+gammazero@users.noreply.github.com> Co-authored-by: guillaumemichel <guillaume@michel.id> Co-authored-by: Daniel Norman <1992255+2color@users.noreply.github.com> Co-authored-by: Hector Sanjuan <code@hector.link>
354 lines
8.5 KiB
Go
354 lines
8.5 KiB
Go
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// GetField retrieves a field from a nested config structure using a dot-separated path
|
|
// Example: GetField(config, "DNS.Resolvers") returns config["DNS"]["Resolvers"]
|
|
func GetField(config map[string]any, path string) (any, bool) {
|
|
parts := strings.Split(path, ".")
|
|
current := config
|
|
|
|
for i, part := range parts {
|
|
// Last part - return the value
|
|
if i == len(parts)-1 {
|
|
val, exists := current[part]
|
|
return val, exists
|
|
}
|
|
|
|
// Navigate deeper
|
|
next, exists := current[part]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
// Ensure it's a map
|
|
nextMap, ok := next.(map[string]any)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
current = nextMap
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// SetField sets a field in a nested config structure using a dot-separated path
|
|
// It creates intermediate maps as needed
|
|
func SetField(config map[string]any, path string, value any) {
|
|
parts := strings.Split(path, ".")
|
|
current := config
|
|
|
|
for i, part := range parts {
|
|
// Last part - set the value
|
|
if i == len(parts)-1 {
|
|
current[part] = value
|
|
return
|
|
}
|
|
|
|
// Navigate or create intermediate maps
|
|
next, exists := current[part]
|
|
if !exists {
|
|
// Create new intermediate map
|
|
newMap := make(map[string]any)
|
|
current[part] = newMap
|
|
current = newMap
|
|
} else {
|
|
// Ensure it's a map
|
|
nextMap, ok := next.(map[string]any)
|
|
if !ok {
|
|
// Can't navigate further, replace with new map
|
|
newMap := make(map[string]any)
|
|
current[part] = newMap
|
|
current = newMap
|
|
} else {
|
|
current = nextMap
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// DeleteField removes a field from a nested config structure
|
|
func DeleteField(config map[string]any, path string) bool {
|
|
parts := strings.Split(path, ".")
|
|
|
|
// Handle simple case
|
|
if len(parts) == 1 {
|
|
_, exists := config[parts[0]]
|
|
delete(config, parts[0])
|
|
return exists
|
|
}
|
|
|
|
// Navigate to parent
|
|
parentPath := strings.Join(parts[:len(parts)-1], ".")
|
|
parent, exists := GetField(config, parentPath)
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
parentMap, ok := parent.(map[string]any)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
fieldName := parts[len(parts)-1]
|
|
_, exists = parentMap[fieldName]
|
|
delete(parentMap, fieldName)
|
|
return exists
|
|
}
|
|
|
|
// MoveField moves a field from one location to another
|
|
func MoveField(config map[string]any, from, to string) error {
|
|
value, exists := GetField(config, from)
|
|
if !exists {
|
|
return fmt.Errorf("source field %s does not exist", from)
|
|
}
|
|
|
|
SetField(config, to, value)
|
|
DeleteField(config, from)
|
|
return nil
|
|
}
|
|
|
|
// RenameField renames a field within the same parent
|
|
func RenameField(config map[string]any, path, oldName, newName string) error {
|
|
var parent map[string]any
|
|
if path == "" {
|
|
parent = config
|
|
} else {
|
|
p, exists := GetField(config, path)
|
|
if !exists {
|
|
return fmt.Errorf("parent path %s does not exist", path)
|
|
}
|
|
var ok bool
|
|
parent, ok = p.(map[string]any)
|
|
if !ok {
|
|
return fmt.Errorf("parent path %s is not a map", path)
|
|
}
|
|
}
|
|
|
|
value, exists := parent[oldName]
|
|
if !exists {
|
|
return fmt.Errorf("field %s does not exist", oldName)
|
|
}
|
|
|
|
parent[newName] = value
|
|
delete(parent, oldName)
|
|
return nil
|
|
}
|
|
|
|
// SetDefault sets a field value only if it doesn't already exist
|
|
func SetDefault(config map[string]any, path string, value any) {
|
|
if _, exists := GetField(config, path); !exists {
|
|
SetField(config, path, value)
|
|
}
|
|
}
|
|
|
|
// TransformField applies a transformation function to a field value
|
|
func TransformField(config map[string]any, path string, transformer func(any) any) error {
|
|
value, exists := GetField(config, path)
|
|
if !exists {
|
|
return fmt.Errorf("field %s does not exist", path)
|
|
}
|
|
|
|
newValue := transformer(value)
|
|
SetField(config, path, newValue)
|
|
return nil
|
|
}
|
|
|
|
// EnsureFieldIs checks if a field equals expected value, sets it if missing
|
|
func EnsureFieldIs(config map[string]any, path string, expected any) {
|
|
current, exists := GetField(config, path)
|
|
if !exists || current != expected {
|
|
SetField(config, path, expected)
|
|
}
|
|
}
|
|
|
|
// MergeInto merges multiple source fields into a destination map
|
|
func MergeInto(config map[string]any, destination string, sources ...string) {
|
|
var destMap map[string]any
|
|
|
|
// Get existing destination if it exists
|
|
if existing, exists := GetField(config, destination); exists {
|
|
if m, ok := existing.(map[string]any); ok {
|
|
destMap = m
|
|
}
|
|
}
|
|
|
|
// Merge each source
|
|
for _, source := range sources {
|
|
if value, exists := GetField(config, source); exists {
|
|
if sourceMap, ok := value.(map[string]any); ok {
|
|
if destMap == nil {
|
|
destMap = make(map[string]any)
|
|
}
|
|
maps.Copy(destMap, sourceMap)
|
|
}
|
|
}
|
|
}
|
|
|
|
if destMap != nil {
|
|
SetField(config, destination, destMap)
|
|
}
|
|
}
|
|
|
|
// CopyField copies a field value to a new location (keeps original)
|
|
func CopyField(config map[string]any, from, to string) error {
|
|
value, exists := GetField(config, from)
|
|
if !exists {
|
|
return fmt.Errorf("source field %s does not exist", from)
|
|
}
|
|
|
|
SetField(config, to, value)
|
|
return nil
|
|
}
|
|
|
|
// ConvertInterfaceSlice converts []interface{} to []string
|
|
func ConvertInterfaceSlice(slice []interface{}) []string {
|
|
result := make([]string, 0, len(slice))
|
|
for _, item := range slice {
|
|
if str, ok := item.(string); ok {
|
|
result = append(result, str)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetOrCreateSection gets or creates a map section in config
|
|
func GetOrCreateSection(config map[string]any, path string) map[string]any {
|
|
existing, exists := GetField(config, path)
|
|
if exists {
|
|
if section, ok := existing.(map[string]any); ok {
|
|
return section
|
|
}
|
|
}
|
|
|
|
// Create new section
|
|
section := make(map[string]any)
|
|
SetField(config, path, section)
|
|
return section
|
|
}
|
|
|
|
// SafeCastMap safely casts to map[string]any with fallback to empty map
|
|
func SafeCastMap(value any) map[string]any {
|
|
if m, ok := value.(map[string]any); ok {
|
|
return m
|
|
}
|
|
return make(map[string]any)
|
|
}
|
|
|
|
// SafeCastSlice safely casts to []interface{} with fallback to empty slice
|
|
func SafeCastSlice(value any) []interface{} {
|
|
if s, ok := value.([]interface{}); ok {
|
|
return s
|
|
}
|
|
return []interface{}{}
|
|
}
|
|
|
|
// ReplaceDefaultsWithAuto replaces default values with "auto" in a map
|
|
func ReplaceDefaultsWithAuto(values map[string]any, defaults map[string]string) map[string]string {
|
|
result := make(map[string]string)
|
|
for k, v := range values {
|
|
if vStr, ok := v.(string); ok {
|
|
if replacement, isDefault := defaults[vStr]; isDefault {
|
|
result[k] = replacement
|
|
} else {
|
|
result[k] = vStr
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// EnsureSliceContains ensures a slice field contains a value
|
|
func EnsureSliceContains(config map[string]any, path string, value string) {
|
|
existing, exists := GetField(config, path)
|
|
if !exists {
|
|
SetField(config, path, []string{value})
|
|
return
|
|
}
|
|
|
|
if slice, ok := existing.([]interface{}); ok {
|
|
// Check if value already exists
|
|
for _, item := range slice {
|
|
if str, ok := item.(string); ok && str == value {
|
|
return // Already contains value
|
|
}
|
|
}
|
|
// Add value
|
|
SetField(config, path, append(slice, value))
|
|
} else if strSlice, ok := existing.([]string); ok {
|
|
if !slices.Contains(strSlice, value) {
|
|
SetField(config, path, append(strSlice, value))
|
|
}
|
|
} else {
|
|
// Replace with new slice containing value
|
|
SetField(config, path, []string{value})
|
|
}
|
|
}
|
|
|
|
// ReplaceInSlice replaces old values with new in a slice field
|
|
func ReplaceInSlice(config map[string]any, path string, oldValue, newValue string) {
|
|
existing, exists := GetField(config, path)
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
if slice, ok := existing.([]interface{}); ok {
|
|
result := make([]string, 0, len(slice))
|
|
for _, item := range slice {
|
|
if str, ok := item.(string); ok {
|
|
if str == oldValue {
|
|
result = append(result, newValue)
|
|
} else {
|
|
result = append(result, str)
|
|
}
|
|
}
|
|
}
|
|
SetField(config, path, result)
|
|
}
|
|
}
|
|
|
|
// GetMapSection gets a map section with error handling
|
|
func GetMapSection(config map[string]any, path string) (map[string]any, error) {
|
|
value, exists := GetField(config, path)
|
|
if !exists {
|
|
return nil, fmt.Errorf("section %s does not exist", path)
|
|
}
|
|
|
|
section, ok := value.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("section %s is not a map", path)
|
|
}
|
|
|
|
return section, nil
|
|
}
|
|
|
|
// CloneStringMap clones a map[string]any to map[string]string
|
|
func CloneStringMap(m map[string]any) map[string]string {
|
|
result := make(map[string]string, len(m))
|
|
for k, v := range m {
|
|
if str, ok := v.(string); ok {
|
|
result[k] = str
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// IsEmptySlice checks if a value is an empty slice
|
|
func IsEmptySlice(value any) bool {
|
|
if value == nil {
|
|
return true
|
|
}
|
|
if slice, ok := value.([]interface{}); ok {
|
|
return len(slice) == 0
|
|
}
|
|
if slice, ok := value.([]string); ok {
|
|
return len(slice) == 0
|
|
}
|
|
return false
|
|
}
|