Skip to main content

lingxia_lxapp/
error.rs

1use lingxia_platform::PlatformError;
2use lingxia_webview::WebViewError;
3#[cfg(feature = "js-appservice")]
4use rong::RongJSError;
5#[cfg(feature = "js-appservice")]
6use rong::error::{ErrorData, ErrorNumber};
7use serde_json::Value;
8use std::io;
9use thiserror::Error;
10
11#[derive(Debug, Clone, Error)]
12pub enum LxAppError {
13    /// Error when performing web operations.
14    #[error("WebView error: {0}")]
15    WebView(String),
16
17    #[error("{0} not found")]
18    ResourceNotFound(String),
19
20    #[error("{0} is not valid JSON file")]
21    InvalidJsonFile(String),
22
23    /// Error for invalid parameters
24    #[error("Invalid parameter: {0}")]
25    InvalidParameter(String),
26
27    /// Error for unsupported operations
28    #[error("Unsupported operation: {0}")]
29    UnsupportedOperation(String),
30
31    /// The lxapp already owns a live presentation in another shell region.
32    #[error("Surface conflict: {0}")]
33    SurfaceConflict(String),
34
35    /// Error for I/O operations (file access, network, etc.)
36    #[error("I/O error: {0}")]
37    IoError(String),
38
39    /// Error for runtime operations
40    #[error("Runtime error: {0}")]
41    Runtime(String),
42
43    /// Error channel operations
44    #[error("Channel error: {0}")]
45    ChannelError(String),
46
47    /// Error when resource is exhausted
48    #[error("Resource exhausted: {0}")]
49    ResourceExhausted(String),
50
51    /// Error when bridge error
52    #[error("Bridge error: {0}")]
53    Bridge(String),
54
55    /// Error for Rong runtime
56    #[error("Rong Error: {0}")]
57    RongJS(String),
58
59    /// Structured Rong host error that preserves code/data metadata
60    #[error("{code}: {message}")]
61    RongJSHost {
62        code: String,
63        message: String,
64        data: Option<Value>,
65    },
66
67    /// Error when plugin is not configured in lxapp.json
68    #[error("Plugin not configured: {0}")]
69    PluginNotConfigured(String),
70
71    /// Error when plugin download fails
72    #[error("Plugin download failed: {0}")]
73    PluginDownloadFailed(String),
74}
75
76impl From<io::Error> for LxAppError {
77    fn from(error: io::Error) -> Self {
78        LxAppError::IoError(error.to_string())
79    }
80}
81
82impl<T> From<std::sync::mpsc::SendError<T>> for LxAppError {
83    fn from(error: std::sync::mpsc::SendError<T>) -> Self {
84        LxAppError::ChannelError(error.to_string())
85    }
86}
87
88impl From<serde_json::Error> for LxAppError {
89    fn from(error: serde_json::Error) -> Self {
90        LxAppError::Bridge(format!("JSON Processing Error: {}", error))
91    }
92}
93
94#[cfg(feature = "js-appservice")]
95impl From<RongJSError> for LxAppError {
96    fn from(error: RongJSError) -> Self {
97        if let Some(host) = error.as_host_error() {
98            let data = host.data.as_ref().map(error_data_to_json);
99            return LxAppError::RongJSHost {
100                code: host.code.to_string(),
101                message: host.message.clone(),
102                data,
103            };
104        }
105        LxAppError::RongJS(error.to_string())
106    }
107}
108
109impl From<PlatformError> for LxAppError {
110    fn from(error: PlatformError) -> Self {
111        match error {
112            PlatformError::NotSupported(message) => LxAppError::UnsupportedOperation(message),
113            PlatformError::InvalidParameter(message) => LxAppError::InvalidParameter(message),
114            PlatformError::AssetNotFound(message) => LxAppError::ResourceNotFound(message),
115            other => LxAppError::Runtime(other.to_string()),
116        }
117    }
118}
119
120impl From<WebViewError> for LxAppError {
121    fn from(error: WebViewError) -> Self {
122        match error {
123            WebViewError::WebView(detail) => LxAppError::WebView(detail),
124            other => LxAppError::WebView(other.to_string()),
125        }
126    }
127}
128
129impl From<lingxia_update::UpdateError> for LxAppError {
130    fn from(error: lingxia_update::UpdateError) -> Self {
131        match error {
132            lingxia_update::UpdateError::InvalidParameter(detail) => {
133                LxAppError::InvalidParameter(detail)
134            }
135            lingxia_update::UpdateError::UnsupportedOperation(detail) => {
136                LxAppError::UnsupportedOperation(detail)
137            }
138            lingxia_update::UpdateError::RequiresRuntimeUpgrade(detail) => {
139                LxAppError::requires_runtime_upgrade(detail)
140            }
141            lingxia_update::UpdateError::ResourceNotFound(detail) => {
142                LxAppError::ResourceNotFound(detail)
143            }
144            lingxia_update::UpdateError::Io(detail) => LxAppError::IoError(detail),
145            lingxia_update::UpdateError::Runtime(detail) => LxAppError::Runtime(detail),
146        }
147    }
148}
149
150impl From<lingxia_settings::SettingsError> for LxAppError {
151    fn from(error: lingxia_settings::SettingsError) -> Self {
152        LxAppError::Runtime(error.to_string())
153    }
154}
155
156#[cfg(feature = "js-appservice")]
157fn error_data_to_json(data: &ErrorData) -> Value {
158    match data {
159        ErrorData::Null => Value::Null,
160        ErrorData::Bool(v) => Value::Bool(*v),
161        ErrorData::String(v) => Value::String(v.clone()),
162        ErrorData::Number(n) => match n {
163            ErrorNumber::I64(v) => Value::Number(serde_json::Number::from(*v)),
164            ErrorNumber::U64(v) => Value::Number(serde_json::Number::from(*v)),
165            ErrorNumber::F64(bits) => {
166                let num = f64::from_bits(*bits);
167                match serde_json::Number::from_f64(num) {
168                    Some(value) => Value::Number(value),
169                    None => Value::String(num.to_string()),
170                }
171            }
172        },
173        ErrorData::Array(items) => Value::Array(items.iter().map(error_data_to_json).collect()),
174        ErrorData::Object(obj) => Value::Object(
175            obj.iter()
176                .map(|(k, v)| (k.clone(), error_data_to_json(v)))
177                .collect(),
178        ),
179    }
180}
181
182const REQUIRES_RUNTIME_UPGRADE_CODE: &str = "6002";
183
184impl LxAppError {
185    /// This host's runtime is below the package's `minRuntime`.
186    pub fn requires_runtime_upgrade(detail: impl Into<String>) -> Self {
187        Self::RongJSHost {
188            code: REQUIRES_RUNTIME_UPGRADE_CODE.to_string(),
189            message: detail.into(),
190            data: None,
191        }
192    }
193
194    pub fn is_requires_runtime_upgrade(&self) -> bool {
195        matches!(self, Self::RongJSHost { code, .. } if code == REQUIRES_RUNTIME_UPGRADE_CODE)
196    }
197
198    /// The message without the variant's prefix.
199    ///
200    /// Every variant's `Display` prepends or appends its own label, which reads
201    /// as noise once the caller has already framed the failure: nesting them
202    /// produces "Invalid parameter: iconPath: Invalid parameter: …", and the
203    /// variants that append produce "directory traversal not allowed not
204    /// found". Use this when embedding one error's reason inside another's
205    /// sentence.
206    pub fn detail(&self) -> Option<&str> {
207        match self {
208            Self::WebView(detail)
209            | Self::ResourceNotFound(detail)
210            | Self::InvalidJsonFile(detail)
211            | Self::InvalidParameter(detail)
212            | Self::UnsupportedOperation(detail)
213            | Self::IoError(detail)
214            | Self::Runtime(detail) => Some(detail),
215            _ => None,
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::LxAppError;
223    use lingxia_webview::WebViewError;
224
225    #[test]
226    fn raw_webview_errors_keep_the_source_label() {
227        assert_eq!(
228            LxAppError::WebView("WebView not ready".to_string()).to_string(),
229            "WebView error: WebView not ready"
230        );
231    }
232
233    #[test]
234    fn typed_webview_errors_have_one_source_label() {
235        let error = LxAppError::from(WebViewError::WebView("creation failed".to_string()));
236        assert_eq!(error.to_string(), "WebView error: creation failed");
237
238        let error = LxAppError::from(WebViewError::InvalidCreateOptions(
239            "missing tag".to_string(),
240        ));
241        assert_eq!(
242            error.to_string(),
243            "WebView error: Invalid WebView create options: missing tag"
244        );
245    }
246}