codexia 0.2.0

OpenAI- and Anthropic-compatible local API gateway backed by Codex OAuth.
Documentation
use axum::{Json, http::StatusCode, response::IntoResponse};
use serde_json::json;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    #[error("HTTP error: {0}")]
    Http(#[from] reqwest::Error),

    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("URL error: {0}")]
    Url(#[from] url::ParseError),

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

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

    #[error("upstream error: {0}")]
    Upstream(String),

    #[error("unauthorized")]
    Unauthorized,
}

pub type Result<T> = std::result::Result<T, Error>;

impl Error {
    pub fn oauth(message: impl Into<String>) -> Self {
        Self::OAuth(message.into())
    }

    pub fn config(message: impl Into<String>) -> Self {
        Self::Config(message.into())
    }

    pub fn upstream(message: impl Into<String>) -> Self {
        Self::Upstream(message.into())
    }

    pub fn status_code(&self) -> StatusCode {
        match self {
            Self::Unauthorized => StatusCode::UNAUTHORIZED,
            Self::Config(_) | Self::OAuth(_) => StatusCode::BAD_REQUEST,
            Self::Upstream(_) | Self::Http(_) => StatusCode::BAD_GATEWAY,
            Self::Io(_) | Self::Json(_) | Self::Url(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

impl IntoResponse for Error {
    fn into_response(self) -> axum::response::Response {
        let status = self.status_code();
        let body = Json(json!({
            "error": {
                "message": self.to_string(),
                "type": match status {
                    StatusCode::UNAUTHORIZED => "authentication_error",
                    StatusCode::BAD_GATEWAY => "upstream_error",
                    _ => "invalid_request_error",
                }
            }
        }));
        (status, body).into_response()
    }
}