orion-accessor 0.6.0

Unified accessor layer for HTTP, Git, and local resources with redirect rules, proxy control, and env-aware templating.
Documentation
use derive_more::From;
use orion_error::{ErrorCode, StructError, UvsReason};
use serde_derive::Serialize;
use std::time::Duration;
use thiserror::Error;

#[derive(Clone, Debug, Serialize, PartialEq, Error, From)]
pub enum AddrReason {
    #[error("unknown")]
    Brief(String),
    #[error("{0}")]
    Uvs(UvsReason),
    #[error("Operation timed out after {timeout:?} and {attempts} attempts")]
    OperationTimeoutExceeded { timeout: Duration, attempts: u32 },
    #[error("Total timeout {total_timeout:?} exceeded after {elapsed:?}")]
    TotalTimeoutExceeded {
        total_timeout: Duration,
        elapsed: Duration,
    },
    #[error("Retry exhausted after {attempts} attempts, last error: {last_error}")]
    RetryExhausted { attempts: u32, last_error: String },
}

impl ErrorCode for AddrReason {
    fn error_code(&self) -> i32 {
        match self {
            AddrReason::Brief(_) => 500,
            AddrReason::Uvs(r) => r.error_code(),
            AddrReason::OperationTimeoutExceeded { .. } => 408,
            AddrReason::TotalTimeoutExceeded { .. } => 408,
            AddrReason::RetryExhausted { .. } => 504,
        }
    }
}

pub type AddrResult<T> = Result<T, StructError<AddrReason>>;
pub type AddrError = StructError<AddrReason>;

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn test_addr_reason_brief() {
        let reason = AddrReason::Brief("test error".to_string());
        assert_eq!(reason.error_code(), 500);
        assert_eq!(reason.to_string(), "unknown");
    }

    #[test]
    fn test_addr_reason_operation_timeout_exceeded() {
        let timeout = Duration::from_secs(30);
        let reason = AddrReason::OperationTimeoutExceeded {
            timeout,
            attempts: 3,
        };
        assert_eq!(reason.error_code(), 408);
        let error_msg = reason.to_string();
        assert!(error_msg.contains("30s"));
        assert!(error_msg.contains("3 attempts"));
    }

    #[test]
    fn test_addr_reason_total_timeout_exceeded() {
        let total_timeout = Duration::from_secs(60);
        let elapsed = Duration::from_secs(65);
        let reason = AddrReason::TotalTimeoutExceeded {
            total_timeout,
            elapsed,
        };
        assert_eq!(reason.error_code(), 408);
        let error_msg = reason.to_string();
        assert!(error_msg.contains("60s"));
        assert!(error_msg.contains("65s"));
    }

    #[test]
    fn test_addr_reason_retry_exhausted() {
        let reason = AddrReason::RetryExhausted {
            attempts: 5,
            last_error: "connection failed".to_string(),
        };
        assert_eq!(reason.error_code(), 504);
        let error_msg = reason.to_string();
        assert!(error_msg.contains("5 attempts"));
        assert!(error_msg.contains("connection failed"));
    }
}