arc-agi-rs 0.1.0

🤖 A Rust client SDK for the ARC-AGI-3 API.
Documentation
// Copyright 2026 Mahmoud Harmouch.
//
// Licensed under the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! # Error Types
//!
//! This module defines the error surface for `arc-agi-rs`.
//!
//! ## Error Hierarchy
//!
//! | Type | Description |
//! |------|-------------|
//! | [`ApiError`] | Structured error codes returned by the ARC-AGI-3 REST API |
//! | [`ArcAgiError`] | Top-level crate error that wraps [`anyhow::Error`] |
//!
//! ## See Also
//!
//! - [ARC-AGI-3 API Reference](https://three.arcprize.org)

use std::error::Error;
use std::fmt;
use std::str::FromStr;

use anyhow::Error as AnyhowError;

/// Structured error codes returned by the ARC-AGI-3 REST API in the
/// `error` field of an error response body.
///
/// # Example
/// ```rust
/// use arc_agi_rs::error::ApiError;
///
/// let code = ApiError::ValidationError;
/// assert_eq!(code.as_str(), "VALIDATION_ERROR");
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ApiError {
    /// The request body or parameters failed validation.
    ValidationError,
    /// The requested game is not available on the server.
    GameNotAvailable,
    /// An unexpected server-side error occurred.
    ServerError,
    /// The game exists but has not been started; send a RESET first.
    GameNotStarted,
}

impl ApiError {
    /// Returns the canonical API error code string.
    pub fn as_str(&self) -> &'static str {
        match self {
            ApiError::ValidationError => "VALIDATION_ERROR",
            ApiError::GameNotAvailable => "GAME_NOT_AVAILABLE_ERROR",
            ApiError::ServerError => "SERVER_ERROR",
            ApiError::GameNotStarted => "GAME_NOT_STARTED_ERROR",
        }
    }
}

impl FromStr for ApiError {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "VALIDATION_ERROR" => Ok(ApiError::ValidationError),
            "GAME_NOT_AVAILABLE_ERROR" => Ok(ApiError::GameNotAvailable),
            "SERVER_ERROR" => Ok(ApiError::ServerError),
            "GAME_NOT_STARTED_ERROR" => Ok(ApiError::GameNotStarted),
            _ => Err(()),
        }
    }
}

impl fmt::Display for ApiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// The top-level error type for `arc-agi-rs`.
///
/// Wraps [`AnyhowError`] to carry rich context chains. Most public
/// methods return `Result<T, ArcAgiError>` aliased as [`crate::Result`].
///
/// # Example
/// ```rust
/// use arc_agi_rs::error::ArcAgiError;
///
/// let err = ArcAgiError::new("connection refused");
/// assert!(err.to_string().contains("connection refused"));
/// ```
#[derive(Debug)]
pub struct ArcAgiError(pub AnyhowError);

impl ArcAgiError {
    /// Creates a new `ArcAgiError` from any displayable message.
    pub fn new(msg: impl fmt::Display) -> Self {
        Self(anyhow::anyhow!("{}", msg))
    }
}

impl fmt::Display for ArcAgiError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl Error for ArcAgiError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.0.source()
    }
}

impl From<AnyhowError> for ArcAgiError {
    fn from(err: AnyhowError) -> Self {
        Self(err)
    }
}

impl From<reqwest::Error> for ArcAgiError {
    fn from(err: reqwest::Error) -> Self {
        Self(AnyhowError::new(err))
    }
}

impl From<serde_json::Error> for ArcAgiError {
    fn from(err: serde_json::Error) -> Self {
        Self(AnyhowError::new(err))
    }
}

// Copyright 2026 Mahmoud Harmouch.
//
// Licensed under the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.