package service import ( "fmt" "strings" "github.com/ez-api/ez-api/internal/model" "github.com/ez-api/foundation/provider" ) // ProviderGroupManager centralizes ProviderGroup defaults and validation. type ProviderGroupManager struct{} func NewProviderGroupManager() *ProviderGroupManager { return &ProviderGroupManager{} } // NormalizeGroup applies type-specific defaults and validates required fields. func (m *ProviderGroupManager) NormalizeGroup(group model.ProviderGroup) (model.ProviderGroup, error) { name := strings.TrimSpace(group.Name) if name == "" { return model.ProviderGroup{}, fmt.Errorf("name required") } group.Name = name ptypeRaw := strings.TrimSpace(group.Type) ptype := provider.NormalizeType(ptypeRaw) if ptype == "" { return model.ProviderGroup{}, fmt.Errorf("type required") } group.Type = ptypeRaw group.BaseURL = strings.TrimSpace(group.BaseURL) group.GoogleProject = strings.TrimSpace(group.GoogleProject) group.GoogleLocation = strings.TrimSpace(group.GoogleLocation) group.StaticHeaders = strings.TrimSpace(group.StaticHeaders) group.HeadersProfile = strings.TrimSpace(group.HeadersProfile) switch ptype { case provider.TypeOpenAI: if group.BaseURL == "" { group.BaseURL = "https://api.openai.com/v1" } case provider.TypeAnthropic, provider.TypeClaude, provider.TypeClaudeCode: if group.BaseURL == "" { group.BaseURL = "https://api.anthropic.com" } case provider.TypeCodex: if group.BaseURL == "" { group.BaseURL = "https://chatgpt.com" } case provider.TypeGeminiCLI: if group.BaseURL == "" { group.BaseURL = "https://cloudcode-pa.googleapis.com" } case provider.TypeAntigravity: if group.BaseURL == "" { group.BaseURL = "https://daily-cloudcode-pa.googleapis.com" } case provider.TypeCompatible: if group.BaseURL == "" { return model.ProviderGroup{}, fmt.Errorf("base_url required for compatible providers") } default: if provider.IsVertexFamily(ptype) { if group.GoogleLocation == "" { group.GoogleLocation = provider.DefaultGoogleLocation(ptype, "") } } else if provider.IsGoogleFamily(ptype) { // Google SDK (gemini/google/aistudio) ignores base_url. group.BaseURL = strings.TrimSpace(group.BaseURL) } } if group.Status == "" { group.Status = "active" } return group, nil } // ValidateAPIKey enforces provider-type requirements for APIKey entries. func (m *ProviderGroupManager) ValidateAPIKey(group model.ProviderGroup, key model.APIKey) error { ptype := provider.NormalizeType(group.Type) if ptype == "" { return fmt.Errorf("provider group type required") } apiKey := strings.TrimSpace(key.APIKey) accessToken := strings.TrimSpace(key.AccessToken) switch { case ptype == provider.TypeCodex || ptype == provider.TypeGeminiCLI || ptype == provider.TypeAntigravity || ptype == provider.TypeClaudeCode: if accessToken == "" { return fmt.Errorf("access_token required") } return nil case provider.IsVertexFamily(ptype): // Vertex uses ADC; api_key can be empty. return nil case provider.IsGoogleFamily(ptype): if apiKey == "" { return fmt.Errorf("api_key required for gemini api providers") } return nil default: if apiKey == "" { return fmt.Errorf("api_key required") } return nil } }