1use gpui::{AnyElement, App};
6use std::fmt;
7use std::sync::Arc;
8
9#[derive(Debug, Clone)]
15pub enum NavigationResult {
16 Success { path: String },
18 NotFound { path: String },
20 Blocked {
22 reason: String,
23 redirect: Option<String>,
24 },
25 Error(NavigationError),
27}
28
29#[derive(Debug, Clone)]
31pub enum NavigationError {
32 RouteNotFound { path: String },
34
35 GuardBlocked { reason: String },
37
38 InvalidParams { message: String },
40
41 NavigationFailed { message: String },
43
44 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 pub fn is_success(&self) -> bool {
75 matches!(self, NavigationResult::Success { .. })
76 }
77
78 pub fn is_not_found(&self) -> bool {
80 matches!(self, NavigationResult::NotFound { .. })
81 }
82
83 pub fn is_blocked(&self) -> bool {
85 matches!(self, NavigationResult::Blocked { .. })
86 }
87
88 pub fn is_error(&self) -> bool {
90 matches!(self, NavigationResult::Error(_))
91 }
92
93 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
105pub type ErrorHandler = Arc<dyn Fn(&mut App, &NavigationError) -> AnyElement + Send + Sync>;
111
112pub type NotFoundHandler = Arc<dyn Fn(&mut App, &str) -> AnyElement + Send + Sync>;
114
115pub struct ErrorHandlers {
117 pub not_found: Option<NotFoundHandler>,
119
120 pub error: Option<ErrorHandler>,
122}
123
124impl ErrorHandlers {
125 pub fn new() -> Self {
127 Self {
128 not_found: None,
129 error: None,
130 }
131 }
132
133 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 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 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 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#[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}