Skip to main content

linkrs_server/permission/
error.rs

1//! Permission Error Type
2//!
3//! Coverage of Permission Management Related Errors.
4//!
5//! ## Design
6//!
7//! `PermissionError` is a struct with boxed source error to keep size small (~24 bytes).
8//! This follows the same pattern as `DBError`, `QueryError`, `StorageError`, and `SessionError`.
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/// Permission operation result type alias
18pub type PermissionResult<T> = Result<T, PermissionError>;
19
20/// Permission error kind enumeration
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum PermissionErrorKind {
23    InsufficientPermission,
24    NoRoleInSpace,
25    PermissionDenied,
26    RoleNotFound,
27    UserNotFound,
28    GrantRoleFailed,
29    RevokeRoleFailed,
30    OnlyGodCanManageSpaces,
31    OnlyGodCanManageUsers,
32    SpaceIdRequired,
33    SchemaSpaceIdRequired,
34    SchemaWriteSpaceIdRequired,
35    SchemaWritePermissionDenied,
36    DataReadSpaceIdRequired,
37    DataWriteSpaceIdRequired,
38    GuestCannotWriteData,
39    CannotReadUserInfo,
40    RoleOperationSpaceIdRequired,
41    RoleOperationTargetRoleRequired,
42    OnlyAdminOrGodCanManageRoles,
43    CannotGrantRole,
44    CannotModifyOwnRole,
45    ChangePasswordTargetUserRequired,
46    CanOnlyChangeOwnPassword,
47}
48
49impl PermissionErrorKind {
50    pub fn as_str(&self) -> &'static str {
51        match self {
52            PermissionErrorKind::InsufficientPermission => "insufficient_permission",
53            PermissionErrorKind::NoRoleInSpace => "no_role_in_space",
54            PermissionErrorKind::PermissionDenied => "permission_denied",
55            PermissionErrorKind::RoleNotFound => "role_not_found",
56            PermissionErrorKind::UserNotFound => "user_not_found",
57            PermissionErrorKind::GrantRoleFailed => "grant_role_failed",
58            PermissionErrorKind::RevokeRoleFailed => "revoke_role_failed",
59            PermissionErrorKind::OnlyGodCanManageSpaces => "only_god_can_manage_spaces",
60            PermissionErrorKind::OnlyGodCanManageUsers => "only_god_can_manage_users",
61            PermissionErrorKind::SpaceIdRequired => "space_id_required",
62            PermissionErrorKind::SchemaSpaceIdRequired => "schema_space_id_required",
63            PermissionErrorKind::SchemaWriteSpaceIdRequired => "schema_write_space_id_required",
64            PermissionErrorKind::SchemaWritePermissionDenied => "schema_write_permission_denied",
65            PermissionErrorKind::DataReadSpaceIdRequired => "data_read_space_id_required",
66            PermissionErrorKind::DataWriteSpaceIdRequired => "data_write_space_id_required",
67            PermissionErrorKind::GuestCannotWriteData => "guest_cannot_write_data",
68            PermissionErrorKind::CannotReadUserInfo => "cannot_read_user_info",
69            PermissionErrorKind::RoleOperationSpaceIdRequired => "role_operation_space_id_required",
70            PermissionErrorKind::RoleOperationTargetRoleRequired => {
71                "role_operation_target_role_required"
72            }
73            PermissionErrorKind::OnlyAdminOrGodCanManageRoles => {
74                "only_admin_or_god_can_manage_roles"
75            }
76            PermissionErrorKind::CannotGrantRole => "cannot_grant_role",
77            PermissionErrorKind::CannotModifyOwnRole => "cannot_modify_own_role",
78            PermissionErrorKind::ChangePasswordTargetUserRequired => {
79                "change_password_target_user_required"
80            }
81            PermissionErrorKind::CanOnlyChangeOwnPassword => "can_only_change_own_password",
82        }
83    }
84}
85
86/// Permission Related Errors
87///
88/// Design principles:
89/// 1. Small size: Uses boxed errors to keep struct size minimal (~24 bytes)
90/// 2. Full context: Preserves error chain
91/// 3. Clone support: Can be cloned for logging/propagation
92#[derive(Debug)]
93pub struct PermissionError {
94    kind: PermissionErrorKind,
95    message: String,
96    source: Option<BoxedError>,
97}
98
99impl PermissionError {
100    pub fn new(kind: PermissionErrorKind, message: impl Into<String>) -> Self {
101        Self {
102            kind,
103            message: message.into(),
104            source: None,
105        }
106    }
107
108    pub fn with_source(mut self, source: BoxedError) -> Self {
109        self.source = Some(source);
110        self
111    }
112
113    pub fn kind(&self) -> PermissionErrorKind {
114        self.kind
115    }
116
117    pub fn message(&self) -> &str {
118        &self.message
119    }
120
121    // Convenience constructors
122    pub fn insufficient_permission() -> Self {
123        Self::new(
124            PermissionErrorKind::InsufficientPermission,
125            "Insufficient permission",
126        )
127    }
128
129    pub fn no_role_in_space(user: impl Into<String>, space_id: i64) -> Self {
130        Self::new(
131            PermissionErrorKind::NoRoleInSpace,
132            format!("User {} has no role in space {}", user.into(), space_id),
133        )
134    }
135
136    pub fn permission_denied(permission: impl Into<String>, user: impl Into<String>) -> Self {
137        Self::new(
138            PermissionErrorKind::PermissionDenied,
139            format!(
140                "Permission denied: {} for user {}",
141                permission.into(),
142                user.into()
143            ),
144        )
145    }
146
147    pub fn role_not_found(role: impl Into<String>) -> Self {
148        Self::new(
149            PermissionErrorKind::RoleNotFound,
150            format!("Role not found: {}", role.into()),
151        )
152    }
153
154    pub fn user_not_found(user: impl Into<String>) -> Self {
155        Self::new(
156            PermissionErrorKind::UserNotFound,
157            format!("User not found: {}", user.into()),
158        )
159    }
160
161    pub fn grant_role_failed(message: impl Into<String>) -> Self {
162        Self::new(PermissionErrorKind::GrantRoleFailed, message)
163    }
164
165    pub fn revoke_role_failed(message: impl Into<String>) -> Self {
166        Self::new(PermissionErrorKind::RevokeRoleFailed, message)
167    }
168
169    pub fn only_god_can_manage_spaces() -> Self {
170        Self::new(
171            PermissionErrorKind::OnlyGodCanManageSpaces,
172            "Permission denied: only Admin or God can create/delete spaces",
173        )
174    }
175
176    pub fn only_god_can_manage_users() -> Self {
177        Self::new(
178            PermissionErrorKind::OnlyGodCanManageUsers,
179            "Permission denied: only Admin or God can manage users",
180        )
181    }
182
183    pub fn space_id_required() -> Self {
184        Self::new(
185            PermissionErrorKind::SpaceIdRequired,
186            "Space ID required for read Space operation",
187        )
188    }
189
190    pub fn schema_space_id_required() -> Self {
191        Self::new(
192            PermissionErrorKind::SchemaSpaceIdRequired,
193            "Space ID required for read Schema operation",
194        )
195    }
196
197    pub fn schema_write_space_id_required() -> Self {
198        Self::new(
199            PermissionErrorKind::SchemaWriteSpaceIdRequired,
200            "Space ID required for write Schema operation",
201        )
202    }
203
204    pub fn schema_write_permission_denied(space_id: i64, user: impl Into<String>) -> Self {
205        Self::new(
206            PermissionErrorKind::SchemaWritePermissionDenied,
207            format!(
208                "Schema write permission denied: user {} has insufficient privileges in space {}",
209                user.into(),
210                space_id
211            ),
212        )
213    }
214
215    pub fn data_read_space_id_required() -> Self {
216        Self::new(
217            PermissionErrorKind::DataReadSpaceIdRequired,
218            "Space ID required for read data operation",
219        )
220    }
221
222    pub fn data_write_space_id_required() -> Self {
223        Self::new(
224            PermissionErrorKind::DataWriteSpaceIdRequired,
225            "Space ID required for write data operation",
226        )
227    }
228
229    pub fn guest_cannot_write_data() -> Self {
230        Self::new(
231            PermissionErrorKind::GuestCannotWriteData,
232            "Guest role has no permission to write data",
233        )
234    }
235
236    pub fn cannot_read_user_info() -> Self {
237        Self::new(
238            PermissionErrorKind::CannotReadUserInfo,
239            "No permission to read user information",
240        )
241    }
242
243    pub fn role_operation_space_id_required() -> Self {
244        Self::new(
245            PermissionErrorKind::RoleOperationSpaceIdRequired,
246            "Space ID required for role operation",
247        )
248    }
249
250    pub fn role_operation_target_role_required() -> Self {
251        Self::new(
252            PermissionErrorKind::RoleOperationTargetRoleRequired,
253            "Target role required for role operation",
254        )
255    }
256
257    pub fn only_admin_or_god_can_manage_roles() -> Self {
258        Self::new(
259            PermissionErrorKind::OnlyAdminOrGodCanManageRoles,
260            "Permission denied: only Admin or God can manage roles",
261        )
262    }
263
264    pub fn cannot_grant_role(role: impl Into<String>) -> Self {
265        Self::new(
266            PermissionErrorKind::CannotGrantRole,
267            format!("Permission denied: cannot grant role {}", role.into()),
268        )
269    }
270
271    pub fn cannot_modify_own_role() -> Self {
272        Self::new(
273            PermissionErrorKind::CannotModifyOwnRole,
274            "Cannot modify own role",
275        )
276    }
277
278    pub fn change_password_target_user_required() -> Self {
279        Self::new(
280            PermissionErrorKind::ChangePasswordTargetUserRequired,
281            "Target user required for change password operation",
282        )
283    }
284
285    pub fn can_only_change_own_password() -> Self {
286        Self::new(
287            PermissionErrorKind::CanOnlyChangeOwnPassword,
288            "Can only change own password",
289        )
290    }
291}
292
293impl std::fmt::Display for PermissionError {
294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295        write!(f, "[{}] {}", self.kind.as_str(), self.message)?;
296        if let Some(ref source) = self.source {
297            write!(f, "\n  Caused by: {}", source)?;
298        }
299        Ok(())
300    }
301}
302
303impl Clone for PermissionError {
304    fn clone(&self) -> Self {
305        Self {
306            kind: self.kind,
307            message: self.message.clone(),
308            source: None,
309        }
310    }
311}
312
313impl std::error::Error for PermissionError {
314    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
315        self.source
316            .as_ref()
317            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
318    }
319}
320
321impl ToPublicError for PermissionError {
322    fn to_public_error(&self) -> PublicError {
323        PublicError::new(self.to_error_code(), self.to_public_message())
324    }
325
326    fn to_error_code(&self) -> ErrorCode {
327        ErrorCode::PermissionDenied
328    }
329
330    fn to_public_message(&self) -> String {
331        self.message.clone()
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_permission_error_size() {
341        assert!(
342            std::mem::size_of::<PermissionError>() <= 64,
343            "PermissionError should be small, got {} bytes",
344            std::mem::size_of::<PermissionError>()
345        );
346    }
347
348    #[test]
349    fn test_permission_error_creation() {
350        let err = PermissionError::role_not_found("admin");
351        assert_eq!(err.kind(), PermissionErrorKind::RoleNotFound);
352        assert!(err.message().contains("admin"));
353    }
354}