Skip to main content

redevplugin_worker_sdk/
error.rs

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