Skip to main content

unitycatalog_common/
error.rs

1/// A convenience type for declaring Results in the Delta Sharing libraries.
2pub type Result<T, E = Error> = std::result::Result<T, E>;
3
4#[derive(Debug, thiserror::Error)]
5pub enum Error {
6    #[error("Entity not found.")]
7    NotFound,
8
9    #[error("Already exists")]
10    AlreadyExists,
11
12    #[error("Invalid table location: {0}")]
13    InvalidTableLocation(String),
14
15    #[error("Invalid Argument: {0}")]
16    InvalidArgument(String),
17
18    #[error("Invalid identifier: {0}")]
19    InvalidIdentifier(#[from] uuid::Error),
20
21    #[error("Generic error: {0}")]
22    Generic(String),
23
24    #[error(transparent)]
25    SerDe(#[from] serde_json::Error),
26
27    #[error("invalid url: {0}")]
28    InvalidUrl(#[from] url::ParseError),
29
30    #[error("Conflict")]
31    Conflict,
32
33    #[error(transparent)]
34    ResourceStore(olai_store::Error),
35}
36
37/// Flatten the store's common error variants onto the native ones so callers can
38/// match `Error::NotFound` / `Error::AlreadyExists` / `Error::Conflict` uniformly
39/// regardless of which backend raised them; anything else is wrapped verbatim.
40impl From<olai_store::Error> for Error {
41    fn from(e: olai_store::Error) -> Self {
42        match e {
43            olai_store::Error::NotFound => Error::NotFound,
44            olai_store::Error::AlreadyExists => Error::AlreadyExists,
45            olai_store::Error::Conflict => Error::Conflict,
46            olai_store::Error::InvalidArgument(msg) => Error::InvalidArgument(msg),
47            olai_store::Error::InvalidIdentifier(err) => Error::InvalidIdentifier(err),
48            other => Error::ResourceStore(other),
49        }
50    }
51}
52
53impl Error {
54    pub fn generic(msg: impl Into<String>) -> Self {
55        Self::Generic(msg.into())
56    }
57
58    pub fn invalid_argument(msg: impl Into<String>) -> Self {
59        Self::InvalidArgument(msg.into())
60    }
61
62    /// Returns a machine-readable error code matching the UC API spec.
63    pub fn error_code(&self) -> &str {
64        match self {
65            Error::NotFound => "RESOURCE_NOT_FOUND",
66            Error::AlreadyExists => "RESOURCE_ALREADY_EXISTS",
67            Error::Conflict => "RESOURCE_CONFLICT",
68            Error::InvalidArgument(_) => "INVALID_PARAMETER_VALUE",
69            Error::InvalidIdentifier(_) => "INVALID_PARAMETER_VALUE",
70            Error::InvalidTableLocation(_) => "INVALID_PARAMETER_VALUE",
71            Error::InvalidUrl(_) => "INVALID_PARAMETER_VALUE",
72            Error::SerDe(_) => "INTERNAL_ERROR",
73            Error::Generic(_) => "INTERNAL_ERROR",
74            // Common store variants are flattened onto the native ones by
75            // `From<olai_store::Error>`; only the residual (Generic/SerDe) reach here.
76            Error::ResourceStore(_) => "INTERNAL_ERROR",
77        }
78    }
79}
80
81#[cfg(feature = "axum")]
82impl Error {
83    /// Maps this error to an HTTP status and a static client-facing message.
84    ///
85    /// Shared by downstream `IntoResponse` impls (server, sharing-client) so the
86    /// status/message table for common variants lives in one place. Callers
87    /// remain responsible for composing the `error_code` and wrapping the result
88    /// in their own `ErrorResponse` body.
89    pub fn response_parts(&self) -> (http::StatusCode, &'static str) {
90        use http::StatusCode;
91
92        const INTERNAL: &str = "The request is not handled correctly due to a server error.";
93        const INVALID: &str = "Invalid argument provided in the request.";
94        const NOT_FOUND: &str = "The requested resource does not exist.";
95        const ALREADY_EXISTS: &str = "The resource already exists.";
96        const CONFLICT: &str = "The request conflicts with the current resource state.";
97
98        match self {
99            Error::NotFound => (StatusCode::NOT_FOUND, NOT_FOUND),
100            Error::AlreadyExists => (StatusCode::CONFLICT, ALREADY_EXISTS),
101            Error::Conflict => (StatusCode::CONFLICT, CONFLICT),
102            Error::InvalidArgument(msg) => {
103                tracing::error!("Invalid argument: {msg}");
104                (StatusCode::BAD_REQUEST, INVALID)
105            }
106            Error::InvalidIdentifier(e) => {
107                tracing::error!("Invalid identifier: {e}");
108                (StatusCode::BAD_REQUEST, INVALID)
109            }
110            Error::InvalidTableLocation(loc) => {
111                tracing::error!("Invalid table location: {loc}");
112                (StatusCode::BAD_REQUEST, INVALID)
113            }
114            Error::InvalidUrl(e) => {
115                tracing::error!("Invalid URL: {e}");
116                (StatusCode::BAD_REQUEST, INVALID)
117            }
118            Error::SerDe(e) => {
119                tracing::error!("Serialization error: {e}");
120                (StatusCode::INTERNAL_SERVER_ERROR, INTERNAL)
121            }
122            Error::Generic(msg) => {
123                tracing::error!("Generic common error: {msg}");
124                (StatusCode::INTERNAL_SERVER_ERROR, INTERNAL)
125            }
126            // Common store variants are flattened onto the native ones by
127            // `From<olai_store::Error>`; only the residual (Generic/SerDe) reach here.
128            Error::ResourceStore(e) => {
129                tracing::error!("Resource store error: {e}");
130                (StatusCode::INTERNAL_SERVER_ERROR, INTERNAL)
131            }
132        }
133    }
134}