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 }