1use axum::extract::Json;
2use axum::http::StatusCode;
3use axum::response::{IntoResponse, Response};
4use tracing::error;
5use unitycatalog_common::ErrorResponse;
6
7pub type Result<T, E = Error> = std::result::Result<T, E>;
8
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11 #[error("Common Error: {source}")]
12 Common {
13 #[from]
14 source: unitycatalog_common::Error,
15 },
16
17 #[error("Object Store Error: {source}")]
18 ObjectStore {
19 #[from]
20 source: object_store::Error,
21 },
22
23 #[error("Serialization Error: {source}")]
24 SerDe {
25 #[from]
26 source: serde_json::Error,
27 },
28
29 #[error("Entity not found.")]
30 NotFound,
31
32 #[error("No or invalid token provided.")]
33 Unauthenticated,
34
35 #[error("Recipient is not allowed to read the entity.")]
36 NotAllowed,
37
38 #[error("Already exists")]
39 AlreadyExists,
40
41 #[error("Commit version conflict: {0}")]
42 CommitVersionConflict(String),
43
44 #[error("Update requirement conflict: {0}")]
45 UpdateRequirementConflict(String),
46
47 #[error("Resource exhausted: {0}")]
48 ResourceExhausted(String),
49
50 #[error("Invalid argument")]
51 InvalidArgument(String),
52
53 #[error("Invalid identifier: {0}")]
54 InvalidIdentifier(#[from] uuid::Error),
55
56 #[error("Missing recipient")]
57 MissingRecipient,
58
59 #[error("Generic error: {0}")]
60 Generic(String),
61
62 #[error("Not implemented: {0}")]
63 NotImplemented(&'static str),
64
65 #[error("Cloud credential error: {source}")]
66 CloudCredential {
67 #[from]
68 source: olai_http::Error,
69 },
70
71 #[error("Resource store error: {source}")]
72 ResourceStore {
73 #[from]
74 source: olai_store::Error,
75 },
76}
77
78impl Error {
79 pub fn generic(message: impl ToString) -> Self {
80 Error::Generic(message.to_string())
81 }
82
83 pub fn invalid_argument(message: impl ToString) -> Self {
84 Error::InvalidArgument(message.to_string())
85 }
86
87 pub fn error_code(&self) -> &str {
89 match self {
90 Error::NotFound => "RESOURCE_NOT_FOUND",
91 Error::AlreadyExists => "RESOURCE_ALREADY_EXISTS",
92 Error::CommitVersionConflict(_) => "COMMIT_VERSION_CONFLICT",
93 Error::UpdateRequirementConflict(_) => "UPDATE_REQUIREMENT_CONFLICT",
94 Error::ResourceExhausted(_) => "RESOURCE_EXHAUSTED",
95 Error::NotAllowed => "PERMISSION_DENIED",
96 Error::Unauthenticated => "UNAUTHENTICATED",
97 Error::InvalidArgument(_) => "INVALID_PARAMETER_VALUE",
98 Error::InvalidIdentifier(_) => "INVALID_PARAMETER_VALUE",
99 Error::MissingRecipient => "INVALID_PARAMETER_VALUE",
100 Error::Common { source } => source.error_code(),
101 Error::ObjectStore { .. } => "INTERNAL_ERROR",
102 Error::SerDe { .. } => "INTERNAL_ERROR",
103 Error::Generic(_) => "INTERNAL_ERROR",
104 Error::NotImplemented(_) => "NOT_IMPLEMENTED",
105 Error::CloudCredential { .. } => "INTERNAL_ERROR",
106 Error::ResourceStore { source } => match source {
107 olai_store::Error::NotFound => "RESOURCE_NOT_FOUND",
108 olai_store::Error::AlreadyExists => "RESOURCE_ALREADY_EXISTS",
109 olai_store::Error::InvalidArgument(_) => "INVALID_PARAMETER_VALUE",
110 olai_store::Error::InvalidIdentifier(_) => "INVALID_PARAMETER_VALUE",
111 _ => "INTERNAL_ERROR",
112 },
113 }
114 }
115}
116
117const INTERNAL_ERROR: (StatusCode, &str) = (
118 StatusCode::INTERNAL_SERVER_ERROR,
119 "The request is not handled correctly due to a server error.",
120);
121
122const INVALID_ARGUMENT: (StatusCode, &str) = (
123 StatusCode::BAD_REQUEST,
124 "Invalid argument provided in the request.",
125);
126
127impl IntoResponse for Error {
128 fn into_response(self) -> Response {
129 let error_code = self.error_code().to_string();
130 let (status, message) = match self {
131 Error::Common { source } => {
132 let (status, message) = source.response_parts();
133 return (
134 status,
135 Json(ErrorResponse {
136 error_code,
137 message: message.to_string(),
138 }),
139 )
140 .into_response();
141 }
142 Error::ObjectStore { source } => match &source {
144 object_store::Error::NotFound { .. } => (
145 StatusCode::NOT_FOUND,
146 "The requested resource does not exist.",
147 ),
148 object_store::Error::AlreadyExists { .. } => {
149 (StatusCode::CONFLICT, "The resource already exists.")
150 }
151 _ => {
152 error!("Object store error: {}", source);
153 INTERNAL_ERROR
154 }
155 },
156 Error::SerDe { source } => {
157 error!("Serialization error: {}", source);
158 INTERNAL_ERROR
159 }
160 Error::NotFound => (
161 StatusCode::NOT_FOUND,
162 "The requested resource does not exist.",
163 ),
164 Error::NotAllowed => (
165 StatusCode::FORBIDDEN,
166 "The request is forbidden from being fulfilled.",
167 ),
168 Error::AlreadyExists => (StatusCode::CONFLICT, "The resource already exists."),
169 Error::CommitVersionConflict(message) => {
170 error!("Commit version conflict: {}", message);
171 (
172 StatusCode::CONFLICT,
173 "The commit version was already accepted by another writer.",
174 )
175 }
176 Error::UpdateRequirementConflict(message) => {
177 error!("Update requirement conflict: {}", message);
178 (
179 StatusCode::CONFLICT,
180 "An update requirement (assert-table-uuid / assert-etag) was not met.",
181 )
182 }
183 Error::ResourceExhausted(message) => {
184 error!("Resource exhausted: {}", message);
185 (
186 StatusCode::TOO_MANY_REQUESTS,
187 "The maximum number of unbackfilled commits for this table was reached.",
188 )
189 }
190 Error::Unauthenticated => (
191 StatusCode::UNAUTHORIZED,
192 "The request is unauthenticated. The bearer token is missing or incorrect.",
193 ),
194 Error::InvalidArgument(message) => {
195 error!("Invalid argument: {}", message);
196 INVALID_ARGUMENT
197 }
198 Error::InvalidIdentifier(e) => {
199 error!("Invalid identifier: {}", e);
200 INVALID_ARGUMENT
201 }
202 Error::Generic(message) => {
203 error!("Generic error: {}", message);
204 INTERNAL_ERROR
205 }
206 Error::NotImplemented(what) => {
207 error!("Not implemented: {}", what);
208 (StatusCode::NOT_IMPLEMENTED, "Endpoint not implemented yet.")
209 }
210 Error::CloudCredential { source } => {
211 error!("Cloud credential error: {}", source);
212 INTERNAL_ERROR
213 }
214 Error::MissingRecipient => {
215 error!("Failed to extract recipient from request");
216 (
217 StatusCode::BAD_REQUEST,
218 "Failed to extract recipient from request",
219 )
220 }
221 Error::ResourceStore { source } => match source {
222 olai_store::Error::NotFound => (
223 StatusCode::NOT_FOUND,
224 "The requested resource does not exist.",
225 ),
226 olai_store::Error::AlreadyExists => {
227 (StatusCode::CONFLICT, "The resource already exists.")
228 }
229 olai_store::Error::InvalidArgument(msg) => {
230 error!("Invalid argument: {}", msg);
231 INVALID_ARGUMENT
232 }
233 _ => {
234 error!("Resource store error: {}", source);
235 INTERNAL_ERROR
236 }
237 },
238 };
239
240 (
241 status,
242 Json(ErrorResponse {
243 error_code,
244 message: message.to_string(),
245 }),
246 )
247 .into_response()
248 }
249}