Files
subconverter-go/internal/generator/provider.go
Rogee 7fcabe0225
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
first commit
2025-09-28 10:05:07 +08:00

184 lines
4.9 KiB
Go

package generator
import (
"fmt"
"net/url"
"strconv"
"strings"
)
// ProviderHealthDefinition captures optional health-check settings for providers.
type ProviderHealthDefinition struct {
Enable *bool
URL string
Interval *int
Lazy *bool
Tolerance *int
Timeout *int
Method string
Headers string
}
// ProviderDefinition represents a parsed proxy provider entry.
type ProviderDefinition struct {
Name string
Type string
URL string
Path string
Interval *int
Flags map[string]bool
Fields map[string]string
Health *ProviderHealthDefinition
}
// ParseProviderDefinition converts an encoded provider definition (query string style)
// into a structured ProviderDefinition instance.
func ParseProviderDefinition(definition string) (*ProviderDefinition, error) {
values, err := url.ParseQuery(definition)
if err != nil {
return nil, fmt.Errorf("invalid provider definition: %v", err)
}
name := strings.TrimSpace(values.Get("name"))
if name == "" {
return nil, fmt.Errorf("custom provider missing name")
}
providerType := strings.TrimSpace(values.Get("type"))
if providerType == "" {
providerType = "http"
}
urlValue := strings.TrimSpace(values.Get("url"))
if urlValue == "" {
return nil, fmt.Errorf("custom provider %s missing url", name)
}
pathValue := strings.TrimSpace(values.Get("path"))
if pathValue == "" {
return nil, fmt.Errorf("custom provider %s missing path", name)
}
def := &ProviderDefinition{
Name: name,
Type: providerType,
URL: urlValue,
Path: pathValue,
Flags: make(map[string]bool),
Fields: make(map[string]string),
}
if raw := strings.TrimSpace(values.Get("interval")); raw != "" {
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
def.Interval = &n
} else {
return nil, fmt.Errorf("invalid interval for provider %s: %v", name, err)
}
}
boolFields := []struct {
Query string
Key string
}{
{"lazy", "lazy"},
{"skip_cert_verify", "skip-cert-verify"},
{"override", "override"},
{"failover", "failover"},
}
for _, field := range boolFields {
if raw := strings.TrimSpace(values.Get(field.Query)); raw != "" {
val, err := strconv.ParseBool(raw)
if err != nil {
return nil, fmt.Errorf("invalid %s for provider %s: %v", field.Query, name, err)
}
def.Flags[field.Key] = val
}
}
stringFields := []string{"header", "headers", "filter", "format", "path_backup"}
for _, key := range stringFields {
if raw := strings.TrimSpace(values.Get(key)); raw != "" {
def.Fields[strings.ReplaceAll(key, "_", "-")] = raw
}
}
health := &ProviderHealthDefinition{}
setHealth := false
if raw := strings.TrimSpace(values.Get("health_enable")); raw != "" {
val, err := strconv.ParseBool(raw)
if err != nil {
return nil, fmt.Errorf("invalid health_enable for provider %s: %v", name, err)
}
health.Enable = &val
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_url")); raw != "" {
health.URL = raw
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_interval")); raw != "" {
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
health.Interval = &n
} else {
return nil, fmt.Errorf("invalid health_interval for provider %s: %v", name, err)
}
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_lazy")); raw != "" {
val, err := strconv.ParseBool(raw)
if err != nil {
return nil, fmt.Errorf("invalid health_lazy for provider %s: %v", name, err)
}
health.Lazy = &val
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_tolerance")); raw != "" {
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
health.Tolerance = &n
} else {
return nil, fmt.Errorf("invalid health_tolerance for provider %s: %v", name, err)
}
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_timeout")); raw != "" {
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
health.Timeout = &n
} else {
return nil, fmt.Errorf("invalid health_timeout for provider %s: %v", name, err)
}
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_method")); raw != "" {
health.Method = raw
setHealth = true
}
if raw := strings.TrimSpace(values.Get("health_headers")); raw != "" {
health.Headers = raw
setHealth = true
}
if setHealth {
def.Health = health
}
// Capture any remaining custom fields not explicitly handled.
for key, vals := range values {
if key == "name" || key == "type" || key == "url" || key == "path" || key == "interval" {
continue
}
if strings.HasPrefix(key, "health_") || key == "lazy" || key == "skip_cert_verify" || key == "override" || key == "failover" || key == "header" || key == "headers" || key == "filter" || key == "format" || key == "path_backup" {
continue
}
if len(vals) == 0 {
continue
}
value := strings.TrimSpace(vals[len(vals)-1])
if value == "" {
continue
}
def.Fields[strings.ReplaceAll(key, "_", "-")] = value
}
return def, nil
}