agent-search 0.5.1

Unified multi-provider search CLI for AI agents — 12 providers, 14 modes, email verification, one binary
use crate::types::{ErrorDetail, ErrorResponse};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum SearchError {
    #[error("API error from {provider}: {message}")]
    Api {
        provider: &'static str,
        code: &'static str,
        message: String,
    },

    #[error("Authentication missing for {provider}")]
    AuthMissing { provider: &'static str },

    #[error("Rate limited by {provider}")]
    RateLimited { provider: &'static str },

    #[error("Configuration error: {0}")]
    Config(String),

    #[error("No providers configured for mode '{0}'")]
    NoProviders(String),

    #[error(transparent)]
    Http(#[from] reqwest::Error),

    #[error(transparent)]
    Rquest(#[from] rquest::Error),

    #[error(transparent)]
    Json(#[from] serde_json::Error),

    #[error(transparent)]
    Io(#[from] std::io::Error),
}

impl SearchError {
    pub fn exit_code(&self) -> i32 {
        match self {
            Self::Config(_) | Self::NoProviders(_) => 2,
            Self::AuthMissing { .. } => 2,
            Self::RateLimited { .. } => 4,
            Self::Api { .. } | Self::Http(_) | Self::Rquest(_) => 1,
            Self::Json(_) | Self::Io(_) => 1,
        }
    }

    pub fn error_code(&self) -> &'static str {
        match self {
            Self::Api { code, .. } => code,
            Self::AuthMissing { .. } => "auth_missing",
            Self::RateLimited { .. } => "rate_limited",
            Self::Config(_) => "config_error",
            Self::NoProviders(_) => "no_providers",
            Self::Http(_) | Self::Rquest(_) => "http_error",
            Self::Json(_) => "json_error",
            Self::Io(_) => "io_error",
        }
    }

    pub fn suggestion(&self) -> Option<String> {
        match self {
            Self::AuthMissing { provider } => Some(format!(
                "Set {}_API_KEY env var or run: search config set keys.{} YOUR_KEY",
                provider.to_uppercase(),
                provider
            )),
            Self::NoProviders(mode) => {
                Some(format!(
                    "No providers configured for mode '{}'. Run: search config check",
                    mode
                ))
            }
            Self::RateLimited { provider } => Some(format!(
                "Rate limited by {}. Wait and retry, or use a different provider: search -p <other>",
                provider
            )),
            _ => None,
        }
    }

    pub fn to_error_response(&self) -> ErrorResponse {
        ErrorResponse {
            version: "1",
            status: "error",
            error: ErrorDetail {
                code: self.error_code().to_string(),
                message: self.to_string(),
                suggestion: self.suggestion(),
            },
        }
    }
}