greentic_types/
error.rs

1//! Shared error types for Greentic crates.
2
3use alloc::string::String;
4
5#[cfg(feature = "schemars")]
6use schemars::JsonSchema;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11#[cfg(feature = "std")]
12use std::error::Error as StdError;
13
14/// Canonical error codes used across the Greentic platform.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[cfg_attr(feature = "schemars", derive(JsonSchema))]
18#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
19pub enum ErrorCode {
20    /// Unclassified error.
21    Unknown,
22    /// Invalid input supplied by the caller.
23    InvalidInput,
24    /// Required entity was not found.
25    NotFound,
26    /// Operation conflicts with existing data.
27    Conflict,
28    /// Operation timed out.
29    Timeout,
30    /// Caller is not authenticated.
31    Unauthenticated,
32    /// Caller lacks permissions.
33    PermissionDenied,
34    /// Requests throttled by rate limits.
35    RateLimited,
36    /// External dependency unavailable.
37    Unavailable,
38    /// Internal platform error.
39    Internal,
40}
41
42/// Error type carrying a code and message.
43#[derive(Debug, Error)]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45#[cfg_attr(feature = "schemars", derive(JsonSchema))]
46#[error("{code:?}: {message}")]
47pub struct GreenticError {
48    /// Machine-readable error code.
49    pub code: ErrorCode,
50    /// Human-readable error message.
51    pub message: String,
52    /// Optional source error for debugging.
53    #[cfg(feature = "std")]
54    #[cfg_attr(feature = "serde", serde(skip, default = "default_source"))]
55    #[cfg_attr(feature = "schemars", schemars(skip))]
56    #[source]
57    source: Option<Box<dyn StdError + Send + Sync>>,
58}
59
60impl GreenticError {
61    /// Creates a new error with the provided code and message.
62    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
63        Self {
64            code,
65            message: message.into(),
66            #[cfg(feature = "std")]
67            source: None,
68        }
69    }
70
71    /// Attaches a source error to the `GreenticError`.
72    #[cfg(feature = "std")]
73    pub fn with_source<E>(mut self, source: E) -> Self
74    where
75        E: StdError + Send + Sync + 'static,
76    {
77        self.source = Some(Box::new(source));
78        self
79    }
80}
81
82#[cfg(feature = "std")]
83fn default_source() -> Option<Box<dyn StdError + Send + Sync>> {
84    None
85}
86
87#[cfg(feature = "time")]
88impl From<time::error::ComponentRange> for GreenticError {
89    fn from(err: time::error::ComponentRange) -> Self {
90        Self::new(ErrorCode::InvalidInput, err.to_string())
91    }
92}
93
94#[cfg(feature = "time")]
95impl From<time::error::Parse> for GreenticError {
96    fn from(err: time::error::Parse) -> Self {
97        Self::new(ErrorCode::InvalidInput, err.to_string())
98    }
99}
100
101#[cfg(feature = "uuid")]
102impl From<uuid::Error> for GreenticError {
103    fn from(err: uuid::Error) -> Self {
104        Self::new(ErrorCode::InvalidInput, err.to_string())
105    }
106}
107
108/// Convenient result alias for Greentic APIs.
109pub type GResult<T> = core::result::Result<T, GreenticError>;