Skip to main content

linkrs_server/session/
error.rs

1//! Session Error Type
2//!
3//! Covering session management related errors.
4//!
5//! ## Design
6//!
7//! `SessionError` is a struct with boxed source error to keep size small (~24 bytes).
8//! This follows the same pattern as `DBError`, `QueryError`, and `StorageError` for consistency.
9
10use std::error::Error;
11
12use linkrs_core::error::codes::{ErrorCode, PublicError, ToPublicError};
13
14/// Thread-safe boxed error type
15type BoxedError = Box<dyn Error + Send + Sync>;
16
17/// Session operation result type alias
18pub type SessionResult<T> = Result<T, SessionError>;
19
20/// Session error kind enumeration
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum SessionErrorKind {
23    SessionNotFound,
24    SessionExpired,
25    MaxConnectionsExceeded,
26    QueryNotFound,
27    KillSessionFailed,
28    ManagerError,
29    InsufficientPermission,
30}
31
32impl SessionErrorKind {
33    pub fn as_str(&self) -> &'static str {
34        match self {
35            SessionErrorKind::SessionNotFound => "session_not_found",
36            SessionErrorKind::SessionExpired => "session_expired",
37            SessionErrorKind::MaxConnectionsExceeded => "max_connections_exceeded",
38            SessionErrorKind::QueryNotFound => "query_not_found",
39            SessionErrorKind::KillSessionFailed => "kill_session_failed",
40            SessionErrorKind::ManagerError => "manager_error",
41            SessionErrorKind::InsufficientPermission => "insufficient_permission",
42        }
43    }
44}
45
46/// Session-related errors
47///
48/// Design principles:
49/// 1. Small size: Uses boxed errors to keep struct size minimal (~24 bytes)
50/// 2. Full context: Preserves error chain
51/// 3. Clone support: Can be cloned for logging/propagation
52#[derive(Debug)]
53pub struct SessionError {
54    kind: SessionErrorKind,
55    message: String,
56    source: Option<BoxedError>,
57}
58
59impl SessionError {
60    pub fn new(kind: SessionErrorKind, message: impl Into<String>) -> Self {
61        Self {
62            kind,
63            message: message.into(),
64            source: None,
65        }
66    }
67
68    pub fn with_source(mut self, source: BoxedError) -> Self {
69        self.source = Some(source);
70        self
71    }
72
73    pub fn kind(&self) -> SessionErrorKind {
74        self.kind
75    }
76
77    pub fn message(&self) -> &str {
78        &self.message
79    }
80
81    // Convenience constructors
82    pub fn session_not_found(session_id: i64) -> Self {
83        Self::new(
84            SessionErrorKind::SessionNotFound,
85            format!("Session not found: {}", session_id),
86        )
87    }
88
89    pub fn session_expired() -> Self {
90        Self::new(SessionErrorKind::SessionExpired, "Session expired")
91    }
92
93    pub fn max_connections_exceeded() -> Self {
94        Self::new(
95            SessionErrorKind::MaxConnectionsExceeded,
96            "Maximum connections exceeded",
97        )
98    }
99
100    pub fn query_not_found(query_id: u32) -> Self {
101        Self::new(
102            SessionErrorKind::QueryNotFound,
103            format!("Query not found: {}", query_id),
104        )
105    }
106
107    pub fn kill_session_failed(message: impl Into<String>) -> Self {
108        Self::new(SessionErrorKind::KillSessionFailed, message)
109    }
110
111    pub fn manager_error(message: impl Into<String>) -> Self {
112        Self::new(SessionErrorKind::ManagerError, message)
113    }
114
115    pub fn insufficient_permission() -> Self {
116        Self::new(
117            SessionErrorKind::InsufficientPermission,
118            "Insufficient permission to perform this operation",
119        )
120    }
121}
122
123impl std::fmt::Display for SessionError {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(f, "[{}] {}", self.kind.as_str(), self.message)?;
126        if let Some(ref source) = self.source {
127            write!(f, "\n  Caused by: {}", source)?;
128        }
129        Ok(())
130    }
131}
132
133impl Clone for SessionError {
134    fn clone(&self) -> Self {
135        Self {
136            kind: self.kind,
137            message: self.message.clone(),
138            source: None,
139        }
140    }
141}
142
143impl std::error::Error for SessionError {
144    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
145        self.source
146            .as_ref()
147            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
148    }
149}
150
151impl ToPublicError for SessionError {
152    fn to_public_error(&self) -> PublicError {
153        PublicError::new(self.to_error_code(), self.to_public_message())
154    }
155
156    fn to_error_code(&self) -> ErrorCode {
157        match self.kind {
158            SessionErrorKind::SessionNotFound => ErrorCode::ResourceNotFound,
159            SessionErrorKind::SessionExpired => ErrorCode::Unauthorized,
160            SessionErrorKind::MaxConnectionsExceeded => ErrorCode::ResourceExhausted,
161            SessionErrorKind::QueryNotFound => ErrorCode::ResourceNotFound,
162            SessionErrorKind::KillSessionFailed => ErrorCode::InternalError,
163            SessionErrorKind::ManagerError => ErrorCode::InternalError,
164            SessionErrorKind::InsufficientPermission => ErrorCode::PermissionDenied,
165        }
166    }
167
168    fn to_public_message(&self) -> String {
169        self.message.clone()
170    }
171}
172
173impl From<SessionError> for linkrs_core::error::DBError {
174    fn from(e: SessionError) -> Self {
175        let msg = e.to_string();
176        linkrs_core::error::DBError::session(msg).with_source(Box::new(e))
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_session_error_size() {
186        assert!(
187            std::mem::size_of::<SessionError>() <= 64,
188            "SessionError should be small, got {} bytes",
189            std::mem::size_of::<SessionError>()
190        );
191    }
192
193    #[test]
194    fn test_session_error_creation() {
195        let err = SessionError::session_not_found(123);
196        assert_eq!(err.kind(), SessionErrorKind::SessionNotFound);
197        assert!(err.message().contains("123"));
198    }
199}