Skip to main content

fslite_core/
error.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4/// A stable, transport-independent category for filesystem failures.
5#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
6#[serde(rename_all = "snake_case")]
7pub enum ErrorCode {
8    /// A path or node name is invalid.
9    InvalidPathOrName,
10    /// A requested node or resource does not exist.
11    NotFound,
12    /// Creating or restoring a resource would collide with an existing one.
13    AlreadyExists,
14    /// An operation does not support the target node kind.
15    WrongNodeType,
16    /// A directory cannot be removed because it has children.
17    DirectoryNotEmpty,
18    /// Resolving a symbolic link exceeded the permitted number of links.
19    LinkLoop,
20    /// A symbolic link target does not exist.
21    BrokenLink,
22    /// An operation attempted to cross a workspace isolation boundary.
23    WorkspaceBoundaryViolation,
24    /// The caller is not permitted to perform the requested operation.
25    PermissionDenied,
26    /// An optimistic concurrency precondition did not match the current revision.
27    RevisionConflict,
28    /// The operation would exceed a configured quota.
29    QuotaExceeded,
30    /// A byte range is malformed or outside the target content.
31    InvalidRange,
32    /// A pagination cursor is malformed or belongs to another workspace.
33    InvalidCursor,
34    /// The storage backend is temporarily unavailable.
35    StorageBusy,
36    /// The storage backend encountered an unexpected internal failure.
37    InternalStorageFailure,
38}
39
40/// A filesystem error with a stable code, human-readable message, and safe details.
41#[derive(Debug, thiserror::Error)]
42#[error("{message}")]
43pub struct FsError {
44    code: ErrorCode,
45    message: String,
46    details: Value,
47}
48
49/// The result type used by filesystem domain operations.
50pub type FsResult<T> = Result<T, FsError>;
51
52impl FsError {
53    /// Creates an error with the supplied stable code, message, and structured details.
54    pub fn new(code: ErrorCode, message: impl Into<String>, details: Value) -> Self {
55        Self {
56            code,
57            message: message.into(),
58            details,
59        }
60    }
61
62    /// Creates an invalid path or name error.
63    pub fn invalid_path_or_name(subject: impl std::fmt::Display) -> Self {
64        Self::for_subject(
65            ErrorCode::InvalidPathOrName,
66            "invalid path or name",
67            subject,
68        )
69    }
70
71    /// Creates a not-found error.
72    pub fn not_found(subject: impl std::fmt::Display) -> Self {
73        Self::for_subject(ErrorCode::NotFound, "not found", subject)
74    }
75
76    /// Creates an already-exists error.
77    pub fn already_exists(subject: impl std::fmt::Display) -> Self {
78        Self::for_subject(ErrorCode::AlreadyExists, "already exists", subject)
79    }
80
81    /// Creates a wrong-node-type error.
82    pub fn wrong_node_type(subject: impl std::fmt::Display) -> Self {
83        Self::for_subject(ErrorCode::WrongNodeType, "wrong node type", subject)
84    }
85
86    /// Creates a directory-not-empty error.
87    pub fn directory_not_empty(subject: impl std::fmt::Display) -> Self {
88        Self::for_subject(ErrorCode::DirectoryNotEmpty, "directory not empty", subject)
89    }
90
91    /// Creates a link-loop error.
92    pub fn link_loop(subject: impl std::fmt::Display) -> Self {
93        Self::for_subject(ErrorCode::LinkLoop, "link loop", subject)
94    }
95
96    /// Creates a broken-link error.
97    pub fn broken_link(subject: impl std::fmt::Display) -> Self {
98        Self::for_subject(ErrorCode::BrokenLink, "broken link", subject)
99    }
100
101    /// Creates a workspace-boundary-violation error.
102    pub fn workspace_boundary_violation(subject: impl std::fmt::Display) -> Self {
103        Self::for_subject(
104            ErrorCode::WorkspaceBoundaryViolation,
105            "workspace boundary violation",
106            subject,
107        )
108    }
109
110    /// Creates a permission-denied error.
111    pub fn permission_denied(subject: impl std::fmt::Display) -> Self {
112        Self::for_subject(ErrorCode::PermissionDenied, "permission denied", subject)
113    }
114
115    /// Creates a revision-conflict error.
116    pub fn revision_conflict(subject: impl std::fmt::Display) -> Self {
117        Self::for_subject(ErrorCode::RevisionConflict, "revision conflict", subject)
118    }
119
120    /// Creates a quota-exceeded error.
121    pub fn quota_exceeded(subject: impl std::fmt::Display) -> Self {
122        Self::for_subject(ErrorCode::QuotaExceeded, "quota exceeded", subject)
123    }
124
125    /// Creates an invalid-range error.
126    pub fn invalid_range(subject: impl std::fmt::Display) -> Self {
127        Self::for_subject(ErrorCode::InvalidRange, "invalid range", subject)
128    }
129
130    /// Creates an invalid-cursor error.
131    pub fn invalid_cursor(subject: impl std::fmt::Display) -> Self {
132        Self::for_subject(ErrorCode::InvalidCursor, "invalid cursor", subject)
133    }
134
135    /// Creates a storage-busy error.
136    pub fn storage_busy(subject: impl std::fmt::Display) -> Self {
137        Self::for_subject(ErrorCode::StorageBusy, "storage busy", subject)
138    }
139
140    /// Creates an internal-storage-failure error.
141    pub fn internal_storage_failure(subject: impl std::fmt::Display) -> Self {
142        Self::for_subject(
143            ErrorCode::InternalStorageFailure,
144            "internal storage failure",
145            subject,
146        )
147    }
148
149    /// Returns the stable machine-readable error code.
150    pub const fn code(&self) -> ErrorCode {
151        self.code
152    }
153
154    /// Returns the human-readable error message.
155    pub fn message(&self) -> &str {
156        &self.message
157    }
158
159    /// Returns the safe structured error details.
160    pub const fn details(&self) -> &Value {
161        &self.details
162    }
163
164    fn for_subject(code: ErrorCode, label: &str, subject: impl std::fmt::Display) -> Self {
165        Self::new(code, format!("{label}: {subject}"), Value::Null)
166    }
167}