Skip to main content

gpui_navigator/
error.rs

1//! Error handling for router
2//!
3//! Provides error types and handlers for navigation failures, 404s, and other routing errors.
4
5use gpui::{AnyElement, App};
6use std::fmt;
7use std::sync::Arc;
8
9// ============================================================================
10// Navigation Result Types
11// ============================================================================
12
13/// Result of a navigation attempt
14#[derive(Debug, Clone)]
15pub enum NavigationResult {
16    /// Navigation succeeded
17    Success { path: String },
18    /// Route not found
19    NotFound { path: String },
20    /// Navigation blocked by guard
21    Blocked {
22        reason: String,
23        redirect: Option<String>,
24    },
25    /// Navigation error
26    Error(NavigationError),
27}
28
29/// Errors that can occur during navigation
30#[derive(Debug, Clone)]
31pub enum NavigationError {
32    /// Route not found
33    RouteNotFound { path: String },
34
35    /// Guard blocked navigation
36    GuardBlocked { reason: String },
37
38    /// Invalid route parameters
39    InvalidParams { message: String },
40
41    /// Navigation failed
42    NavigationFailed { message: String },
43
44    /// Custom error
45    Custom { message: String },
46}
47
48impl fmt::Display for NavigationError {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            NavigationError::RouteNotFound { path } => {
52                write!(f, "Route not found: {}", path)
53            }
54            NavigationError::GuardBlocked { reason } => {
55                write!(f, "Navigation blocked: {}", reason)
56            }
57            NavigationError::InvalidParams { message } => {
58                write!(f, "Invalid parameters: {}", message)
59            }
60            NavigationError::NavigationFailed { message } => {
61                write!(f, "Navigation failed: {}", message)
62            }
63            NavigationError::Custom { message } => {
64                write!(f, "{}", message)
65            }
66        }
67    }
68}
69
70impl std::error::Error for NavigationError {}
71
72impl NavigationResult {
73    /// Check if navigation was successful
74    pub fn is_success(&self) -> bool {
75        matches!(self, NavigationResult::Success { .. })
76    }
77
78    /// Check if route was not found
79    pub fn is_not_found(&self) -> bool {
80        matches!(self, NavigationResult::NotFound { .. })
81    }
82
83    /// Check if navigation was blocked
84    pub fn is_blocked(&self) -> bool {
85        matches!(self, NavigationResult::Blocked { .. })
86    }
87
88    /// Check if there was an error
89    pub fn is_error(&self) -> bool {
90        matches!(self, NavigationResult::Error(_))
91    }
92
93    /// Get redirect path if blocked with redirect
94    pub fn redirect_path(&self) -> Option<&str> {
95        match self {
96            NavigationResult::Blocked {
97                redirect: Some(path),
98                ..
99            } => Some(path),
100            _ => None,
101        }
102    }
103}
104
105// ============================================================================
106// Error Handlers
107// ============================================================================
108
109/// Handler for navigation errors
110pub type ErrorHandler = Arc<dyn Fn(&mut App, &NavigationError) -> AnyElement + Send + Sync>;
111
112/// Handler for 404 not found
113pub type NotFoundHandler = Arc<dyn Fn(&mut App, &str) -> AnyElement + Send + Sync>;
114
115/// Collection of error handlers for the router
116pub struct ErrorHandlers {
117    /// Handler for 404 not found errors
118    pub not_found: Option<NotFoundHandler>,
119
120    /// Handler for general navigation errors
121    pub error: Option<ErrorHandler>,
122}
123
124impl ErrorHandlers {
125    /// Create new empty error handlers
126    pub fn new() -> Self {
127        Self {
128            not_found: None,
129            error: None,
130        }
131    }
132
133    /// Set the 404 not found handler
134    pub fn on_not_found<F>(mut self, handler: F) -> Self
135    where
136        F: Fn(&mut App, &str) -> AnyElement + Send + Sync + 'static,
137    {
138        self.not_found = Some(Arc::new(handler));
139        self
140    }
141
142    /// Set the general error handler
143    pub fn on_error<F>(mut self, handler: F) -> Self
144    where
145        F: Fn(&mut App, &NavigationError) -> AnyElement + Send + Sync + 'static,
146    {
147        self.error = Some(Arc::new(handler));
148        self
149    }
150
151    /// Render a 404 not found page
152    pub fn render_not_found(&self, cx: &mut App, path: &str) -> Option<AnyElement> {
153        self.not_found.as_ref().map(|handler| handler(cx, path))
154    }
155
156    /// Render an error page
157    pub fn render_error(&self, cx: &mut App, error: &NavigationError) -> Option<AnyElement> {
158        self.error.as_ref().map(|handler| handler(cx, error))
159    }
160}
161
162impl Default for ErrorHandlers {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168// ============================================================================
169// Tests
170// ============================================================================
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use gpui::{div, IntoElement, ParentElement, TestAppContext};
176
177    #[test]
178    fn test_navigation_result_success() {
179        let result = NavigationResult::Success {
180            path: "/home".to_string(),
181        };
182        assert!(result.is_success());
183        assert!(!result.is_not_found());
184        assert!(!result.is_blocked());
185        assert!(!result.is_error());
186    }
187
188    #[test]
189    fn test_navigation_result_not_found() {
190        let result = NavigationResult::NotFound {
191            path: "/invalid".to_string(),
192        };
193        assert!(!result.is_success());
194        assert!(result.is_not_found());
195    }
196
197    #[test]
198    fn test_navigation_result_blocked_with_redirect() {
199        let result = NavigationResult::Blocked {
200            reason: "Not authenticated".to_string(),
201            redirect: Some("/login".to_string()),
202        };
203        assert!(result.is_blocked());
204        assert_eq!(result.redirect_path(), Some("/login"));
205    }
206
207    #[test]
208    fn test_navigation_error_display() {
209        let error = NavigationError::RouteNotFound {
210            path: "/test".to_string(),
211        };
212        assert_eq!(error.to_string(), "Route not found: /test");
213    }
214
215    #[test]
216    fn test_error_handlers_creation() {
217        let handlers = ErrorHandlers::new();
218        assert!(handlers.not_found.is_none());
219        assert!(handlers.error.is_none());
220    }
221
222    #[gpui::test]
223    async fn test_on_not_found(cx: &mut TestAppContext) {
224        let handlers = ErrorHandlers::new()
225            .on_not_found(|_cx, path| div().child(format!("404: {}", path)).into_any_element());
226
227        assert!(handlers.not_found.is_some());
228
229        let element = cx.update(|cx| handlers.render_not_found(cx, "/invalid"));
230        assert!(element.is_some());
231    }
232
233    #[gpui::test]
234    async fn test_on_error(cx: &mut TestAppContext) {
235        let handlers = ErrorHandlers::new()
236            .on_error(|_cx, error| div().child(format!("Error: {}", error)).into_any_element());
237
238        assert!(handlers.error.is_some());
239
240        let error = NavigationError::RouteNotFound {
241            path: "/test".to_string(),
242        };
243
244        let element = cx.update(|cx| handlers.render_error(cx, &error));
245        assert!(element.is_some());
246    }
247}