Skip to main content

openclaw_node/
error.rs

1//! Structured error types for JavaScript consumption.
2
3use serde::{Deserialize, Serialize};
4
5/// Structured error for JavaScript consumption.
6///
7/// This provides rich error information that can be easily handled
8/// in JavaScript code with error codes, status, and retry hints.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct OpenClawError {
11    /// Error code for programmatic handling (e.g., "`PROVIDER_ERROR`", "`AUTH_ERROR`")
12    pub code: String,
13    /// Human-readable error message
14    pub message: String,
15    /// Additional context as JSON (optional)
16    pub details: Option<serde_json::Value>,
17    /// HTTP status code if applicable (for API errors)
18    pub status: Option<u16>,
19    /// Retry after seconds (for rate limits)
20    pub retry_after: Option<u32>,
21}
22
23impl OpenClawError {
24    /// Create a new error with just code and message.
25    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
26        Self {
27            code: code.into(),
28            message: message.into(),
29            details: None,
30            status: None,
31            retry_after: None,
32        }
33    }
34
35    /// Create an error from a provider error.
36    #[must_use]
37    pub fn from_provider_error(e: openclaw_providers::ProviderError) -> Self {
38        use openclaw_providers::ProviderError;
39
40        match &e {
41            ProviderError::Api { status, message } => Self {
42                code: "PROVIDER_API_ERROR".to_string(),
43                message: message.clone(),
44                details: None,
45                status: Some(*status),
46                retry_after: None,
47            },
48            ProviderError::RateLimited { retry_after_secs } => Self {
49                code: "RATE_LIMITED".to_string(),
50                message: "Rate limited by provider".to_string(),
51                details: None,
52                status: Some(429),
53                retry_after: Some(*retry_after_secs as u32),
54            },
55            ProviderError::Network(err) => Self {
56                code: "NETWORK_ERROR".to_string(),
57                message: err.to_string(),
58                details: None,
59                status: None,
60                retry_after: None,
61            },
62            ProviderError::Config(msg) => Self {
63                code: "CONFIG_ERROR".to_string(),
64                message: msg.clone(),
65                details: None,
66                status: Some(400),
67                retry_after: None,
68            },
69            ProviderError::Serialization(err) => Self {
70                code: "SERIALIZATION_ERROR".to_string(),
71                message: err.to_string(),
72                details: None,
73                status: None,
74                retry_after: None,
75            },
76        }
77    }
78
79    /// Create an error from a credential error.
80    #[must_use]
81    pub fn from_credential_error(e: openclaw_core::secrets::CredentialError) -> Self {
82        use openclaw_core::secrets::CredentialError;
83
84        match &e {
85            CredentialError::NotFound(name) => Self {
86                code: "CREDENTIAL_NOT_FOUND".to_string(),
87                message: format!("Credential not found: {name}"),
88                details: None,
89                status: Some(404),
90                retry_after: None,
91            },
92            CredentialError::Crypto(msg) => Self {
93                code: "CRYPTO_ERROR".to_string(),
94                message: msg.clone(),
95                details: None,
96                status: None,
97                retry_after: None,
98            },
99            _ => Self {
100                code: "CREDENTIAL_ERROR".to_string(),
101                message: e.to_string(),
102                details: None,
103                status: None,
104                retry_after: None,
105            },
106        }
107    }
108
109    /// Create a config error.
110    pub fn config_error(message: impl Into<String>) -> Self {
111        Self::new("CONFIG_ERROR", message)
112    }
113
114    /// Create an event store error.
115    pub fn event_store_error(message: impl Into<String>) -> Self {
116        Self::new("EVENT_STORE_ERROR", message)
117    }
118
119    /// Create a validation error.
120    pub fn validation_error(message: impl Into<String>) -> Self {
121        Self::new("VALIDATION_ERROR", message)
122    }
123
124    /// Create a tool error.
125    pub fn tool_error(message: impl Into<String>) -> Self {
126        Self::new("TOOL_ERROR", message)
127    }
128
129    /// Create an agent error.
130    pub fn agent_error(message: impl Into<String>) -> Self {
131        Self::new("AGENT_ERROR", message)
132    }
133}
134
135impl From<OpenClawError> for napi::Error {
136    fn from(e: OpenClawError) -> Self {
137        // Serialize the full error as JSON for structured handling in JS
138        let json = serde_json::to_string(&e).unwrap_or_else(|_| e.message.clone());
139        Self::from_reason(json)
140    }
141}
142
143/// Helper to convert any error to a `napi::Error` with an `OpenClawError`.
144pub fn to_napi_error(code: &str, e: impl std::fmt::Display) -> napi::Error {
145    OpenClawError::new(code, e.to_string()).into()
146}