Files

165 lines
3.4 KiB
Go

package util
import (
"encoding/json"
"fmt"
"strings"
)
// GetByPath retrieves a value from a nested map using dot-notation path.
// e.g. GetByPath(node, "ws-opts.headers.Host")
func GetByPath(input map[string]any, path string) any {
keys := strings.Split(path, ".")
var current any = input
for _, key := range keys {
if current == nil {
return nil
}
m, ok := current.(map[string]any)
if !ok {
return nil
}
current = m[key]
}
return current
}
// SetByPath sets a value in a nested map using dot-notation path,
// creating intermediate maps as needed.
func SetByPath(input map[string]any, path string, value any) map[string]any {
keys := strings.Split(path, ".")
current := input
for i := 0; i < len(keys)-1; i++ {
key := keys[i]
next, ok := current[key].(map[string]any)
if !ok {
next = map[string]any{}
current[key] = next
}
current = next
}
current[keys[len(keys)-1]] = value
return input
}
func GetString(input map[string]any, path string) string {
v := GetByPath(input, path)
if s, ok := v.(string); ok {
return s
}
return ""
}
func GetInt(input map[string]any, path string) int {
v := GetByPath(input, path)
switch n := v.(type) {
case int:
return n
case int64:
return int(n)
case float64:
return int(n)
case string:
var i int
fmt.Sscanf(n, "%d", &i)
return i
}
return 0
}
func GetBool(input map[string]any, path string) bool {
v := GetByPath(input, path)
if b, ok := v.(bool); ok {
return b
}
return false
}
// DeepCopy produces a deep copy of a ProxyNode via JSON round-trip.
func DeepCopy(node map[string]any) map[string]any {
data, err := json.Marshal(node)
if err != nil {
return map[string]any{}
}
var copy map[string]any
if err := json.Unmarshal(data, &copy); err != nil {
return map[string]any{}
}
return copy
}
// StripUndefined removes keys with nil or empty-string values from a map.
func StripUndefined(input map[string]any) map[string]any {
result := make(map[string]any, len(input))
for k, v := range input {
if v == nil {
continue
}
if s, ok := v.(string); ok && s == "" {
continue
}
result[k] = v
}
return result
}
// MergeDeep recursively merges `next` into `base`, returning a new map.
func MergeDeep(base, next map[string]any) map[string]any {
result := make(map[string]any, len(base))
for k, v := range base {
result[k] = v
}
for k, v := range next {
if v == nil {
continue
}
if isPlainObject(v) && isPlainObject(result[k]) {
result[k] = MergeDeep(result[k].(map[string]any), v.(map[string]any))
} else {
result[k] = v
}
}
return result
}
func isPlainObject(v any) bool {
_, ok := v.(map[string]any)
return ok
}
// ToId normalizes a string into a valid record ID: lowercase, non-alphanum → dash, max 64.
func ToId(input string) string {
s := strings.TrimSpace(input)
b := make([]byte, 0, len(s))
prevDash := false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= 'A' && c <= 'Z' {
c += 32
}
if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' {
b = append(b, c)
prevDash = false
} else if c == '-' {
if !prevDash {
b = append(b, '-')
prevDash = true
}
} else {
if !prevDash && len(b) > 0 {
b = append(b, '-')
prevDash = true
}
}
}
// trim leading/trailing dashes
result := strings.Trim(string(b), "-")
if len(result) > 64 {
result = result[:64]
}
if result == "" {
return "item"
}
return result
}