Files
sub-store/internal/proxy/normalize.go
T

187 lines
4.0 KiB
Go

package proxy
import (
"encoding/json"
"fmt"
"strconv"
"github.com/peterqiu0516/sub-store/internal/model"
)
// NormalizeProxy ensures name/type are strings and port is a number,
// then strips undefined (nil and "") values.
func NormalizeProxy(input map[string]any) model.ProxyNode {
if input == nil {
return nil
}
result := make(map[string]any, len(input)+3)
for k, v := range input {
result[k] = v
}
if name, ok := result["name"]; ok {
result["name"] = toString(name)
} else {
result["name"] = ""
}
if t, ok := result["type"]; ok {
result["type"] = toString(t)
} else {
result["type"] = ""
}
if port, exists := result["port"]; exists {
result["port"] = toNumberOrUndefined(port)
}
return StripUndefined(result)
}
// IsProxyNode returns true if the node has both a name and a type.
func IsProxyNode(node model.ProxyNode) bool {
if node == nil {
return false
}
name, ok := node["name"].(string)
if !ok || name == "" {
return false
}
t, ok := node["type"].(string)
if !ok || t == "" {
return false
}
return true
}
// StripUndefined removes nil and "" values from a map.
// This mirrors the TS stripUndefined which filters out undefined and "".
func StripUndefined(input map[string]any) map[string]any {
if input == nil {
return 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
}
// AddPreviewIds adds an "id" field to each proxy computed by StableProxyId.
func AddPreviewIds(proxies []model.ProxyNode) []model.ProxyNode {
result := make([]model.ProxyNode, len(proxies))
for i, proxy := range proxies {
id := StableProxyId(proxy, i)
merged := make(map[string]any, len(proxy)+1)
merged["id"] = id
for k, v := range proxy {
merged[k] = v
}
result[i] = merged
}
return result
}
// StableProxyId builds a deterministic identifier from name|type|server|port|index.
func StableProxyId(proxy model.ProxyNode, index int) string {
return fmt.Sprintf("%s|%s|%s|%s|%d",
toString(proxy["name"]),
toString(proxy["type"]),
toString(proxy["server"]),
portString(proxy["port"]),
index,
)
}
// EnsureUniqueProxyNames appends -2, -3, etc. to duplicate proxy names.
func EnsureUniqueProxyNames(proxies []model.ProxyNode) []model.ProxyNode {
seen := make(map[string]int, len(proxies))
result := make([]model.ProxyNode, len(proxies))
for i, proxy := range proxies {
if proxy == nil {
result[i] = proxy
continue
}
name := toString(proxy["name"])
count := seen[name]
seen[name] = count + 1
if count == 0 {
result[i] = proxy
continue
}
merged := make(map[string]any, len(proxy))
for k, v := range proxy {
merged[k] = v
}
merged["name"] = fmt.Sprintf("%s-%d", name, count+1)
result[i] = merged
}
return result
}
// toString converts an any value to its string representation.
func toString(v any) string {
if v == nil {
return ""
}
switch val := v.(type) {
case string:
return val
case bool:
return strconv.FormatBool(val)
case float64:
return strconv.FormatFloat(val, 'f', -1, 64)
case float32:
return strconv.FormatFloat(float64(val), 'f', -1, 32)
case int:
return strconv.Itoa(val)
case int64:
return strconv.FormatInt(val, 10)
case json.Number:
return val.String()
default:
return fmt.Sprintf("%v", v)
}
}
// portString returns the string representation of a port value.
func portString(v any) string {
if v == nil {
return ""
}
return toString(v)
}
// toNumberOrUndefined converts a value to a float64 (Go's JSON number),
// returning nil if the value is nil or cannot be parsed.
func toNumberOrUndefined(v any) any {
if v == nil {
return nil
}
switch val := v.(type) {
case float64:
return val
case float32:
return float64(val)
case int:
return float64(val)
case int64:
return float64(val)
case string:
if val == "" {
return nil
}
n, err := strconv.ParseFloat(val, 64)
if err != nil {
return nil
}
return n
case bool:
return nil
default:
return nil
}
}