1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
6#[serde(rename_all = "snake_case")]
7pub enum ErrorCode {
8 InvalidPathOrName,
10 NotFound,
12 AlreadyExists,
14 WrongNodeType,
16 DirectoryNotEmpty,
18 LinkLoop,
20 BrokenLink,
22 WorkspaceBoundaryViolation,
24 PermissionDenied,
26 RevisionConflict,
28 QuotaExceeded,
30 InvalidRange,
32 InvalidCursor,
34 StorageBusy,
36 InternalStorageFailure,
38}
39
40#[derive(Debug, thiserror::Error)]
42#[error("{message}")]
43pub struct FsError {
44 code: ErrorCode,
45 message: String,
46 details: Value,
47}
48
49pub type FsResult<T> = Result<T, FsError>;
51
52impl FsError {
53 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 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 pub fn not_found(subject: impl std::fmt::Display) -> Self {
73 Self::for_subject(ErrorCode::NotFound, "not found", subject)
74 }
75
76 pub fn already_exists(subject: impl std::fmt::Display) -> Self {
78 Self::for_subject(ErrorCode::AlreadyExists, "already exists", subject)
79 }
80
81 pub fn wrong_node_type(subject: impl std::fmt::Display) -> Self {
83 Self::for_subject(ErrorCode::WrongNodeType, "wrong node type", subject)
84 }
85
86 pub fn directory_not_empty(subject: impl std::fmt::Display) -> Self {
88 Self::for_subject(ErrorCode::DirectoryNotEmpty, "directory not empty", subject)
89 }
90
91 pub fn link_loop(subject: impl std::fmt::Display) -> Self {
93 Self::for_subject(ErrorCode::LinkLoop, "link loop", subject)
94 }
95
96 pub fn broken_link(subject: impl std::fmt::Display) -> Self {
98 Self::for_subject(ErrorCode::BrokenLink, "broken link", subject)
99 }
100
101 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 pub fn permission_denied(subject: impl std::fmt::Display) -> Self {
112 Self::for_subject(ErrorCode::PermissionDenied, "permission denied", subject)
113 }
114
115 pub fn revision_conflict(subject: impl std::fmt::Display) -> Self {
117 Self::for_subject(ErrorCode::RevisionConflict, "revision conflict", subject)
118 }
119
120 pub fn quota_exceeded(subject: impl std::fmt::Display) -> Self {
122 Self::for_subject(ErrorCode::QuotaExceeded, "quota exceeded", subject)
123 }
124
125 pub fn invalid_range(subject: impl std::fmt::Display) -> Self {
127 Self::for_subject(ErrorCode::InvalidRange, "invalid range", subject)
128 }
129
130 pub fn invalid_cursor(subject: impl std::fmt::Display) -> Self {
132 Self::for_subject(ErrorCode::InvalidCursor, "invalid cursor", subject)
133 }
134
135 pub fn storage_busy(subject: impl std::fmt::Display) -> Self {
137 Self::for_subject(ErrorCode::StorageBusy, "storage busy", subject)
138 }
139
140 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 pub const fn code(&self) -> ErrorCode {
151 self.code
152 }
153
154 pub fn message(&self) -> &str {
156 &self.message
157 }
158
159 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}