1use crate::RuntimeCapability;
16use std::fmt::{Display, Formatter};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum FileDescriptorError {
22 InvalidBudget {
23 limit: usize,
24 },
25 ConfigurationConflict {
26 configured: usize,
27 requested: usize,
28 },
29 BudgetExceeded {
30 requested: usize,
31 available: usize,
32 limit: usize,
33 },
34 OsLimit {
35 requested: usize,
36 os_code: Option<i32>,
37 },
38}
39
40impl Display for FileDescriptorError {
41 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
42 match self {
43 Self::InvalidBudget { limit } => write!(formatter, "file descriptor budget must be positive: {limit}"),
44 Self::ConfigurationConflict { configured, requested } => write!(formatter,
45 "project file descriptor budget conflict: configured {configured}, requested {requested}"),
46 Self::BudgetExceeded { requested, available, limit } => write!(formatter,
47 "project file descriptor budget exceeded: requested {requested}, available {available}, limit {limit}"),
48 Self::OsLimit { requested, os_code } => write!(formatter,
49 "operating system file descriptor limit: requested {requested}, OS code {os_code:?}"),
50 }
51 }
52}
53
54impl std::error::Error for FileDescriptorError {}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum HawDBError {
58 Parse(String),
59 Semantic(String),
60 Storage(String),
61 FileDescriptors(FileDescriptorError),
62 StorageIntegrity(String),
63 Execution(String),
64 TransactionConflict {
65 read_epoch: u64,
66 committed_epoch: u64,
67 key: String,
68 },
69 AppendSequenceExhausted {
70 table: String,
71 watermark: i64,
72 requested: usize,
73 },
74 CapabilityUnavailable {
75 capability: RuntimeCapability,
76 },
77 BranchCommandUnsupported {
78 command: &'static str,
79 context: &'static str,
80 },
81 BranchBusy {
82 resource: &'static str,
83 },
84}
85
86impl Display for HawDBError {
87 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88 match self {
89 HawDBError::Parse(message) => write!(f, "parse error: {message}"),
90 HawDBError::Semantic(message) => write!(f, "semantic error: {message}"),
91 HawDBError::Storage(message) => write!(f, "storage error: {message}"),
92 HawDBError::FileDescriptors(error) => Display::fmt(error, f),
93 HawDBError::StorageIntegrity(message) => {
94 write!(f, "storage integrity error: {message}")
95 }
96 HawDBError::Execution(message) => write!(f, "execution error: {message}"),
97 HawDBError::TransactionConflict {
98 read_epoch,
99 committed_epoch,
100 key,
101 } => write!(
102 f,
103 "transaction conflict on {key}: transaction read epoch {read_epoch}, conflicting commit epoch {committed_epoch}"
104 ),
105 HawDBError::AppendSequenceExhausted {
106 table,
107 watermark,
108 requested,
109 } => write!(
110 f,
111 "append commit sequence exhausted for table {table}: watermark={watermark}, requested={requested}"
112 ),
113 HawDBError::CapabilityUnavailable { capability } => {
114 write!(f, "capability unavailable: {}", capability.as_str())
115 }
116 HawDBError::BranchCommandUnsupported { command, context } => {
117 write!(f, "{command} is unavailable in {context}")
118 }
119 HawDBError::BranchBusy { resource } => write!(f, "branch is busy: {resource}"),
120 }
121 }
122}
123
124impl std::error::Error for HawDBError {
125 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
126 match self {
127 Self::FileDescriptors(error) => Some(error),
128 _ => None,
129 }
130 }
131}
132
133impl HawDBError {
134 pub fn from_storage_error(error: impl std::error::Error + 'static) -> Self {
136 if let Some(error) = (&error as &dyn std::error::Error).downcast_ref::<Self>() {
137 return error.clone();
138 }
139 if let Some(resource) = file_descriptor_error(&error) {
140 Self::FileDescriptors(resource)
141 } else {
142 Self::Storage(error.to_string())
143 }
144 }
145
146 pub fn from_storage_error_with_context(
150 error: impl std::error::Error + 'static,
151 context: &str,
152 ) -> Self {
153 match Self::from_storage_error(error) {
154 Self::Storage(message) => Self::Storage(format!("{context}: {message}")),
155 error => error,
156 }
157 }
158
159 pub const fn is_retryable_transaction_conflict(&self) -> bool {
160 matches!(self, Self::TransactionConflict { .. })
161 }
162}
163
164pub type Result<T> = std::result::Result<T, HawDBError>;
165
166impl From<std::io::Error> for HawDBError {
167 fn from(error: std::io::Error) -> Self {
168 Self::from_storage_error(error)
169 }
170}
171
172pub fn file_descriptor_error(
173 error: &(dyn std::error::Error + 'static),
174) -> Option<FileDescriptorError> {
175 let mut current = Some(error);
176 while let Some(error) = current {
177 if let Some(resource) = error.downcast_ref::<FileDescriptorError>() {
178 return Some(resource.clone());
179 }
180 if let Some(HawDBError::FileDescriptors(resource)) = error.downcast_ref::<HawDBError>() {
181 return Some(resource.clone());
182 }
183 if let Some(error) = error.downcast_ref::<std::io::Error>() {
184 if let Some(code) = error.raw_os_error()
185 && descriptor_os_limit(code)
186 {
187 return Some(FileDescriptorError::OsLimit {
188 requested: 1,
189 os_code: Some(code),
190 });
191 }
192 if let Some(inner) = error.get_ref()
195 && let Some(resource) = file_descriptor_error(inner)
196 {
197 return Some(resource);
198 }
199 }
200 current = error.source();
201 }
202 None
203}
204
205fn descriptor_os_limit(code: i32) -> bool {
206 #[cfg(unix)]
207 {
208 matches!(code, 23 | 24)
209 }
210 #[cfg(windows)]
211 {
212 code == 4
213 }
214 #[cfg(not(any(unix, windows)))]
215 {
216 let _ = code;
217 false
218 }
219}