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