lenso-platform-core 0.1.15

Core runtime primitives for the Lenso backend framework.
Documentation
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{Debug, Display};
use thiserror::Error;

pub type AppResult<T> = Result<T, AppError>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorCode {
    Validation,
    Unauthorized,
    Forbidden,
    NotFound,
    Conflict,
    RateLimited,
    ExternalDependency,
    Internal,
}

impl ErrorCode {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Validation => "validation_failed",
            Self::Unauthorized => "unauthorized",
            Self::Forbidden => "forbidden",
            Self::NotFound => "not_found",
            Self::Conflict => "conflict",
            Self::RateLimited => "rate_limited",
            Self::ExternalDependency => "external_dependency_failure",
            Self::Internal => "internal_error",
        }
    }
}

#[derive(Error)]
pub struct AppError {
    pub code: ErrorCode,
    pub public_message: String,
    pub retryable: bool,
    pub details: Vec<ErrorDetail>,
    #[source]
    source: Option<Box<dyn Error + Send + Sync>>,
}

impl Debug for AppError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("AppError")
            .field("code", &self.code)
            .field("public_message", &self.public_message)
            .field("retryable", &self.retryable)
            .field("details", &self.details)
            .finish_non_exhaustive()
    }
}

impl Display for AppError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}: {}", self.code.as_str(), self.public_message)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ErrorDetail {
    pub field: Option<String>,
    pub reason: String,
}

impl AppError {
    pub fn new(code: ErrorCode, public_message: impl Into<String>) -> Self {
        Self {
            code,
            public_message: public_message.into(),
            retryable: false,
            details: Vec::new(),
            source: None,
        }
    }

    pub fn validation(public_message: impl Into<String>, details: Vec<ErrorDetail>) -> Self {
        Self {
            code: ErrorCode::Validation,
            public_message: public_message.into(),
            retryable: false,
            details,
            source: None,
        }
    }

    pub fn with_source(mut self, source: impl Error + Send + Sync + 'static) -> Self {
        self.source = Some(Box::new(source));
        self
    }

    pub fn retryable(mut self) -> Self {
        self.retryable = true;
        self
    }
}