claude-code-api 0.1.3

OpenAI-compatible API gateway for Claude Code CLI
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, thiserror::Error)]
#[allow(dead_code)]
pub enum ApiError {
    #[error("Internal server error: {0}")]
    Internal(String),
    
    #[error("Bad request: {0}")]
    BadRequest(String),
    
    #[error("Unauthorized: {0}")]
    Unauthorized(String),
    
    #[error("Not found: {0}")]
    NotFound(String),
    
    #[error("Claude process error: {0}")]
    ClaudeProcess(String),
    
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
    
    #[error("Configuration error: {0}")]
    Config(#[from] config::ConfigError),
    
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),
    
    #[error("Timeout error: {0}")]
    Timeout(String),
    
    #[error("Rate limit exceeded: {0}")]
    RateLimit(String),
    
    #[error("Service unavailable: {0}")]
    ServiceUnavailable(String),
    
    #[error("Invalid model: {0}")]
    InvalidModel(String),
    
    #[error("Context length exceeded: {0}")]
    ContextLengthExceeded(String),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
    pub error: ErrorDetail,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorDetail {
    pub message: String,
    pub r#type: String,
    pub param: Option<String>,
    pub code: Option<String>,
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, error_type, code) = match &self {
            ApiError::BadRequest(_) => (StatusCode::BAD_REQUEST, "invalid_request_error", None),
            ApiError::Unauthorized(_) => (StatusCode::UNAUTHORIZED, "authentication_error", None),
            ApiError::NotFound(_) => (StatusCode::NOT_FOUND, "not_found_error", None),
            ApiError::ClaudeProcess(_) => (StatusCode::INTERNAL_SERVER_ERROR, "claude_process_error", None),
            ApiError::Timeout(_) => (StatusCode::GATEWAY_TIMEOUT, "timeout_error", Some("timeout")),
            ApiError::RateLimit(_) => (StatusCode::TOO_MANY_REQUESTS, "rate_limit_error", Some("rate_limit_exceeded")),
            ApiError::ServiceUnavailable(_) => (StatusCode::SERVICE_UNAVAILABLE, "service_unavailable", None),
            ApiError::InvalidModel(_) => (StatusCode::BAD_REQUEST, "invalid_request_error", Some("invalid_model")),
            ApiError::ContextLengthExceeded(_) => (StatusCode::BAD_REQUEST, "invalid_request_error", Some("context_length_exceeded")),
            _ => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error", None),
        };

        let error_response = ErrorResponse {
            error: ErrorDetail {
                message: self.to_string(),
                r#type: error_type.to_string(),
                param: None,
                code: code.map(String::from),
            },
        };

        (status, Json(error_response)).into_response()
    }
}

pub type ApiResult<T> = Result<T, ApiError>;