1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
7pub enum ErrorCode {
8 InvalidArgument,
9 PermissionDenied,
10 NotFound,
11 NotEmpty,
12 CrossDevice,
13 AlreadyExists,
14 ResourceClosed,
15 Canceled,
16 Timeout,
17 WouldBlock,
18 IoError,
19 MountUnavailable,
20 NetworkError,
21 ResourceLimit,
22 Internal,
23 RuntimeUnavailable,
24 RedirectRequiresReplay,
25 #[serde(other)]
26 Unknown,
27}
28
29impl ErrorCode {
30 pub fn as_str(&self) -> &'static str {
31 match self {
32 Self::InvalidArgument => "INVALID_ARGUMENT",
33 Self::PermissionDenied => "PERMISSION_DENIED",
34 Self::NotFound => "NOT_FOUND",
35 Self::NotEmpty => "NOT_EMPTY",
36 Self::CrossDevice => "CROSS_DEVICE",
37 Self::AlreadyExists => "ALREADY_EXISTS",
38 Self::ResourceClosed => "RESOURCE_CLOSED",
39 Self::Canceled => "CANCELED",
40 Self::Timeout => "TIMEOUT",
41 Self::WouldBlock => "WOULD_BLOCK",
42 Self::IoError => "IO_ERROR",
43 Self::MountUnavailable => "MOUNT_UNAVAILABLE",
44 Self::NetworkError => "NETWORK_ERROR",
45 Self::ResourceLimit => "RESOURCE_LIMIT",
46 Self::Internal => "INTERNAL",
47 Self::RuntimeUnavailable => "RUNTIME_UNAVAILABLE",
48 Self::RedirectRequiresReplay => "REDIRECT_REQUIRES_REPLAY",
49 Self::Unknown => "UNKNOWN",
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct Error {
56 pub code: ErrorCode,
57 pub message: String,
58 #[serde(default)]
59 pub retryable: bool,
60 #[serde(default)]
61 pub details: Value,
62}
63
64impl Error {
65 pub(crate) fn internal(message: impl Into<String>) -> Self {
66 Self {
67 code: ErrorCode::Internal,
68 message: message.into(),
69 retryable: false,
70 details: Value::Null,
71 }
72 }
73
74 pub(crate) fn from_abi_status(status: i32) -> Self {
75 let code = match status {
76 -1 => ErrorCode::InvalidArgument,
77 -2 => ErrorCode::PermissionDenied,
78 -3 => ErrorCode::NotFound,
79 -4 => ErrorCode::AlreadyExists,
80 -5 => ErrorCode::ResourceClosed,
81 -6 => ErrorCode::Canceled,
82 -7 => ErrorCode::Timeout,
83 -8 => ErrorCode::WouldBlock,
84 -9 => ErrorCode::IoError,
85 -10 => ErrorCode::NetworkError,
86 -11 => ErrorCode::ResourceLimit,
87 -12 => ErrorCode::Internal,
88 -13 => ErrorCode::RuntimeUnavailable,
89 -14 => ErrorCode::RedirectRequiresReplay,
90 -15 => ErrorCode::MountUnavailable,
91 _ => ErrorCode::Unknown,
92 };
93 Self {
94 code,
95 message: format!("Worker API hostcall failed with ABI status {status}"),
96 retryable: false,
97 details: Value::Null,
98 }
99 }
100}
101
102impl fmt::Display for Error {
103 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(formatter, "{:?}: {}", self.code, self.message)
105 }
106}
107
108impl std::error::Error for Error {}
109
110pub type Result<T> = std::result::Result<T, Error>;
111
112#[cfg(test)]
113mod tests {
114 use super::{Error, ErrorCode};
115
116 #[test]
117 fn preserves_selected_file_codes_and_unknown_mutation() {
118 for (code, expected) in [
119 ("NOT_EMPTY", ErrorCode::NotEmpty),
120 ("CROSS_DEVICE", ErrorCode::CrossDevice),
121 ] {
122 let raw = serde_json::json!({"code":code,"message":"file operation","retryable":false,"details":{"mutation_outcome":"unknown"}});
123 let decoded: Error = serde_json::from_value(raw).unwrap();
124 assert_eq!(decoded.code, expected);
125 assert_eq!(decoded.details["mutation_outcome"], "unknown");
126 assert_eq!(decoded.code.as_str(), code);
127 }
128 }
129
130 #[test]
131 fn preserves_mount_unavailable_abi_status() {
132 assert_eq!(
133 Error::from_abi_status(-15).code,
134 ErrorCode::MountUnavailable
135 );
136 }
137}