linkrs_server/http/
error.rs1use axum::{
2 http::StatusCode,
3 response::{IntoResponse, Response},
4 Json,
5};
6use serde_json::json;
7use std::fmt;
8
9#[derive(Debug)]
10pub enum HttpError {
11 BadRequest(String),
12 Unauthorized(String),
13 Forbidden(String),
14 NotFound(String),
15 Conflict(String),
16 InternalError(String),
17}
18
19impl fmt::Display for HttpError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 HttpError::BadRequest(msg) => write!(f, "Bad Request: {}", msg),
23 HttpError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
24 HttpError::Forbidden(msg) => write!(f, "Forbidden: {}", msg),
25 HttpError::NotFound(msg) => write!(f, "Not Found: {}", msg),
26 HttpError::Conflict(msg) => write!(f, "Conflict: {}", msg),
27 HttpError::InternalError(msg) => write!(f, "Internal Error: {}", msg),
28 }
29 }
30}
31
32impl std::error::Error for HttpError {}
33
34impl IntoResponse for HttpError {
35 fn into_response(self) -> Response {
36 let (status, message) = match self {
37 HttpError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
38 HttpError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
39 HttpError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg),
40 HttpError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
41 HttpError::Conflict(msg) => (StatusCode::CONFLICT, msg),
42 HttpError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
43 };
44
45 let body = Json(json!({
46 "error": message,
47 "status": status.as_u16(),
48 }));
49
50 (status, body).into_response()
51 }
52}
53
54impl HttpError {
55 pub fn bad_request<T: Into<String>>(msg: T) -> Self {
57 HttpError::BadRequest(msg.into())
58 }
59
60 pub fn not_found<T: Into<String>>(msg: T) -> Self {
62 HttpError::NotFound(msg.into())
63 }
64
65 pub fn unauthorized<T: Into<String>>(msg: T) -> Self {
67 HttpError::Unauthorized(msg.into())
68 }
69
70 pub fn forbidden<T: Into<String>>(msg: T) -> Self {
72 HttpError::Forbidden(msg.into())
73 }
74
75 pub fn internal<T: Into<String>>(msg: T) -> Self {
77 HttpError::InternalError(msg.into())
78 }
79
80 pub fn transaction_message<T: Into<String>>(message: T) -> Self {
83 let message = message.into();
84 if message.contains("[transaction_not_found]") {
85 Self::NotFound(message)
86 } else if message.contains("[transaction_not_owner]") {
87 Self::Unauthorized(message)
88 } else if message.contains("[write_transaction_conflict]") {
89 Self::Conflict(message)
90 } else if message.contains("[transaction_timeout]")
91 || message.contains("[transaction_expired]")
92 {
93 Self::BadRequest(message)
94 } else if message.contains("[invalid_state") || message.contains("[savepoint") {
95 Self::Conflict(message)
96 } else {
97 Self::InternalError(message)
98 }
99 }
100}
101
102impl From<linkrs_api::api_core::CoreError> for HttpError {
103 fn from(err: linkrs_api::api_core::CoreError) -> Self {
104 use linkrs_api::api_core::CoreError;
105 match err {
106 CoreError::NotFound(msg) => HttpError::NotFound(msg),
107 CoreError::InvalidParameter(msg) => HttpError::BadRequest(msg),
108 CoreError::QueryExecutionFailed(msg) => HttpError::InternalError(msg),
109 CoreError::TransactionFailed(msg) => HttpError::transaction_message(msg),
110 CoreError::SchemaOperationFailed(msg) => HttpError::InternalError(msg),
111 CoreError::StorageError(msg) => HttpError::InternalError(msg),
112 CoreError::Internal(msg) => HttpError::InternalError(msg),
113 CoreError::DetailedQueryError { message, .. } => HttpError::InternalError(message),
114 CoreError::SyncError(msg) => HttpError::InternalError(msg),
115 CoreError::VectorError(msg) => HttpError::InternalError(msg),
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::HttpError;
123
124 #[test]
125 fn transaction_error_codes_survive_handler_context() {
126 assert!(matches!(
127 HttpError::transaction_message("Failed to commit: [transaction_not_owner] denied"),
128 HttpError::Unauthorized(_)
129 ));
130 assert!(matches!(
131 HttpError::transaction_message("Failed to commit: [write_transaction_conflict]"),
132 HttpError::Conflict(_)
133 ));
134 assert!(matches!(
135 HttpError::transaction_message("Failed to commit: [transaction_timeout]"),
136 HttpError::BadRequest(_)
137 ));
138 }
139}