1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3#![allow(clippy::multiple_crate_versions)]
4
5use std::{
6 collections::BTreeMap,
7 pin::Pin,
8 sync::{Arc, RwLock},
9};
10
11use bytes::Bytes;
12use flume::{Receiver, Sender};
13use futures::Future;
14use hyperchad_renderer::Content;
15pub use hyperchad_transformer::{Container, Element};
16use qstring::QString;
17use switchy::http::models::Method;
18use thiserror::Error;
19use tokio::task::JoinHandle;
20
21pub static DEFAULT_CLIENT_INFO: std::sync::LazyLock<std::sync::Arc<ClientInfo>> =
22 std::sync::LazyLock::new(|| {
23 let os_name = os_info::get().os_type().to_string();
24 std::sync::Arc::new(ClientInfo {
25 os: ClientOs { name: os_name },
26 })
27 });
28
29pub type RouteFunc = Arc<
30 Box<
31 dyn (Fn(
32 RouteRequest,
33 ) -> Pin<
34 Box<
35 dyn Future<Output = Result<Option<Content>, Box<dyn std::error::Error>>> + Send,
36 >,
37 >) + Send
38 + Sync,
39 >,
40>;
41
42#[cfg(feature = "serde")]
43#[derive(Debug, Error)]
44pub enum ParseError {
45 #[error(transparent)]
46 SerdeJson(#[from] serde_json::Error),
47 #[error(transparent)]
48 SerdeUrlEncoded(#[from] serde_urlencoded::de::Error),
49 #[error("Missing body")]
50 MissingBody,
51 #[error("Invalid Content-Type")]
52 InvalidContentType,
53 #[cfg(feature = "form")]
54 #[error(transparent)]
55 IO(#[from] std::io::Error),
56 #[cfg(feature = "form")]
57 #[error("Missing boundary")]
58 MissingBoundary,
59 #[cfg(feature = "form")]
60 #[error(transparent)]
61 ParseUtf8(#[from] std::string::FromUtf8Error),
62 #[cfg(feature = "form")]
63 #[error(transparent)]
64 Multipart(#[from] mime_multipart::Error),
65 #[cfg(feature = "form")]
66 #[error("Invalid Content‑Disposition")]
67 InvalidContentDisposition,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Default)]
71pub struct ClientOs {
72 pub name: String,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct ClientInfo {
77 pub os: ClientOs,
78}
79
80impl Default for ClientInfo {
81 fn default() -> Self {
82 DEFAULT_CLIENT_INFO.as_ref().clone()
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Default)]
87pub struct RequestInfo {
88 pub client: Arc<ClientInfo>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct RouteRequest {
93 pub path: String,
94 pub method: Method,
95 pub query: BTreeMap<String, String>,
96 pub headers: BTreeMap<String, String>,
97 pub cookies: BTreeMap<String, String>,
98 pub info: RequestInfo,
99 pub body: Option<Arc<Bytes>>,
100}
101
102impl RouteRequest {
103 #[must_use]
104 pub fn from_path(path: &str, info: RequestInfo) -> Self {
105 let (path, query) = if let Some((path, query)) = path.split_once('?') {
106 (path, query)
107 } else {
108 (path, "")
109 };
110
111 Self {
112 path: path.to_owned(),
113 method: Method::Get,
114 query: QString::from(query).into_iter().collect(),
115 headers: BTreeMap::new(),
116 cookies: BTreeMap::new(),
117 info,
118 body: None,
119 }
120 }
121
122 #[must_use]
123 pub fn content_type(&self) -> Option<&str> {
124 self.headers.get("content-type").map(String::as_str)
125 }
126
127 #[cfg(feature = "form")]
132 pub fn parse_form<T: serde::de::DeserializeOwned>(&self) -> Result<T, ParseError> {
133 use std::io::{Cursor, Read as _};
134
135 use base64::engine::{Engine as _, general_purpose};
136 use hyper_old::header::{ContentDisposition, ContentType, DispositionParam, Headers};
137 use mime_multipart::{Node, read_multipart_body};
138 use mime_old::Mime;
139 use serde_json::{Map, Value};
140
141 fn parse_multipart_json_sync(body: &[u8], content_type: &str) -> Result<Value, ParseError> {
142 fn process_nodes(
143 nodes: Vec<Node>,
144 obj: &mut Map<String, Value>,
145 ) -> Result<(), ParseError> {
146 for node in nodes {
147 match node {
148 Node::Part(part) => {
149 let cd = part
150 .headers
151 .get::<ContentDisposition>()
152 .ok_or(ParseError::InvalidContentDisposition)?;
153 let field_name = cd
154 .parameters
155 .iter()
156 .find_map(|param| {
157 if let DispositionParam::Ext(key, val) = param {
158 if key.eq_ignore_ascii_case("name") {
159 return Some(val.clone());
160 }
161 }
162 None
163 })
164 .ok_or(ParseError::InvalidContentDisposition)?;
165
166 let text = String::from_utf8(part.body)?;
167 obj.insert(field_name, Value::String(text));
168 }
169
170 Node::File(filepart) => {
171 let cd = filepart
172 .headers
173 .get::<ContentDisposition>()
174 .ok_or(ParseError::InvalidContentDisposition)?;
175 let field_name = cd
176 .parameters
177 .iter()
178 .find_map(|param| {
179 if let DispositionParam::Ext(key, val) = param {
180 if key.eq_ignore_ascii_case("name") {
181 return Some(val.clone());
182 }
183 }
184 None
185 })
186 .ok_or(ParseError::InvalidContentDisposition)?;
187
188 let mut f = std::fs::File::open(&filepart.path)?;
189 let mut data = Vec::new();
190 f.read_to_end(&mut data)?;
191
192 let b64 = general_purpose::STANDARD.encode(&data);
194 obj.insert(field_name, Value::String(b64));
195 }
196
197 Node::Multipart((_hdrs, subparts)) => {
198 process_nodes(subparts, obj)?;
199 }
200 }
201 }
202 Ok(())
203 }
204
205 let mut headers = Headers::new();
206 let mime_type: Mime = content_type
207 .parse()
208 .map_err(|()| ParseError::InvalidContentType)?;
209 headers.set(ContentType(mime_type));
210
211 let mut cursor = Cursor::new(body);
212 let parts: Vec<Node> = read_multipart_body(&mut cursor, &headers, false)?;
213
214 let mut obj = Map::new();
215 process_nodes(parts, &mut obj)?;
216
217 Ok(Value::Object(obj))
218 }
219
220 if let Some(form) = &self.body {
221 let value = parse_multipart_json_sync(
222 form,
223 self.content_type().ok_or(ParseError::InvalidContentType)?,
224 )?;
225 Ok(serde_json::from_value(value)?)
226 } else {
227 Err(ParseError::MissingBody)
228 }
229 }
230
231 #[cfg(feature = "serde")]
236 pub fn parse_body<T: serde::de::DeserializeOwned>(&self) -> Result<T, ParseError> {
237 if let Some(body) = &self.body {
238 Ok(serde_json::from_slice(body)?)
239 } else {
240 Err(ParseError::MissingBody)
241 }
242 }
243}
244
245impl From<Navigation> for RouteRequest {
246 fn from(value: Navigation) -> Self {
247 Self {
248 path: value.0,
249 method: Method::Get,
250 query: BTreeMap::new(),
251 headers: BTreeMap::new(),
252 cookies: BTreeMap::new(),
253 info: RequestInfo { client: value.1 },
254 body: None,
255 }
256 }
257}
258
259impl From<&Navigation> for RouteRequest {
260 fn from(value: &Navigation) -> Self {
261 value.clone().into()
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
266pub enum RoutePath {
267 Literal(String),
268 Literals(Vec<String>),
269 LiteralPrefix(String),
270}
271
272impl RoutePath {
273 #[must_use]
274 pub fn matches(&self, path: &str) -> bool {
275 match self {
276 Self::Literal(route_path) => route_path == path,
277 Self::Literals(route_paths) => route_paths.iter().any(|x| x == path),
278 Self::LiteralPrefix(route_path) => path.starts_with(route_path),
279 }
280 }
281
282 #[must_use]
283 pub fn strip_match<'a>(&'a self, path: &'a str) -> Option<&'a str> {
284 const EMPTY: &str = "";
285
286 match self {
287 Self::Literal(..) | Self::Literals(..) => {
288 if self.matches(path) {
289 Some(EMPTY)
290 } else {
291 None
292 }
293 }
294 Self::LiteralPrefix(route_path) => path.strip_prefix(route_path),
295 }
296 }
297}
298
299impl From<&str> for RoutePath {
300 fn from(value: &str) -> Self {
301 Self::Literal(value.to_owned())
302 }
303}
304
305impl From<&String> for RoutePath {
306 fn from(value: &String) -> Self {
307 Self::Literal(value.to_owned())
308 }
309}
310
311impl From<&[&str; 1]> for RoutePath {
312 fn from(value: &[&str; 1]) -> Self {
313 Self::Literals(value.iter().map(ToString::to_string).collect())
314 }
315}
316
317impl From<&[&str; 2]> for RoutePath {
318 fn from(value: &[&str; 2]) -> Self {
319 Self::Literals(value.iter().map(ToString::to_string).collect())
320 }
321}
322
323impl From<&[&str; 3]> for RoutePath {
324 fn from(value: &[&str; 3]) -> Self {
325 Self::Literals(value.iter().map(ToString::to_string).collect())
326 }
327}
328
329impl From<&[&str; 4]> for RoutePath {
330 fn from(value: &[&str; 4]) -> Self {
331 Self::Literals(value.iter().map(ToString::to_string).collect())
332 }
333}
334
335impl From<&[&str; 5]> for RoutePath {
336 fn from(value: &[&str; 5]) -> Self {
337 Self::Literals(value.iter().map(ToString::to_string).collect())
338 }
339}
340
341impl From<&[&str; 6]> for RoutePath {
342 fn from(value: &[&str; 6]) -> Self {
343 Self::Literals(value.iter().map(ToString::to_string).collect())
344 }
345}
346
347impl From<&[&str; 7]> for RoutePath {
348 fn from(value: &[&str; 7]) -> Self {
349 Self::Literals(value.iter().map(ToString::to_string).collect())
350 }
351}
352
353impl From<&[&str; 8]> for RoutePath {
354 fn from(value: &[&str; 8]) -> Self {
355 Self::Literals(value.iter().map(ToString::to_string).collect())
356 }
357}
358
359impl From<&[&str; 9]> for RoutePath {
360 fn from(value: &[&str; 9]) -> Self {
361 Self::Literals(value.iter().map(ToString::to_string).collect())
362 }
363}
364
365impl From<&[&str; 10]> for RoutePath {
366 fn from(value: &[&str; 10]) -> Self {
367 Self::Literals(value.iter().map(ToString::to_string).collect())
368 }
369}
370
371impl From<&[&str]> for RoutePath {
372 fn from(value: &[&str]) -> Self {
373 Self::Literals(value.iter().map(ToString::to_string).collect())
374 }
375}
376
377impl From<Vec<&str>> for RoutePath {
378 fn from(value: Vec<&str>) -> Self {
379 Self::Literals(value.into_iter().map(ToString::to_string).collect())
380 }
381}
382
383impl From<String> for RoutePath {
384 fn from(value: String) -> Self {
385 Self::Literal(value)
386 }
387}
388
389impl From<&[String]> for RoutePath {
390 fn from(value: &[String]) -> Self {
391 Self::Literals(value.iter().map(ToString::to_string).collect())
392 }
393}
394
395impl From<&[&String]> for RoutePath {
396 fn from(value: &[&String]) -> Self {
397 Self::Literals(value.iter().map(ToString::to_string).collect())
398 }
399}
400
401impl From<Vec<String>> for RoutePath {
402 fn from(value: Vec<String>) -> Self {
403 Self::Literals(value)
404 }
405}
406
407#[derive(Debug, Error)]
408pub enum NavigateError {
409 #[error("Invalid path")]
410 InvalidPath,
411 #[error("Handler error: {0:?}")]
412 Handler(Box<dyn std::error::Error + Send + Sync>),
413 #[error("Sender error")]
414 Sender,
415}
416
417#[derive(Clone)]
418pub struct Router {
419 #[cfg(feature = "static-routes")]
420 pub static_routes: Arc<RwLock<Vec<(RoutePath, RouteFunc)>>>,
421 pub routes: Arc<RwLock<Vec<(RoutePath, RouteFunc)>>>,
422 sender: Sender<Content>,
423 pub receiver: Receiver<Content>,
424}
425
426impl std::fmt::Debug for Router {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 f.debug_struct("Router")
429 .field("sender", &self.sender)
430 .field("receiver", &self.receiver)
431 .finish_non_exhaustive()
432 }
433}
434
435impl Default for Router {
436 fn default() -> Self {
437 Self::new()
438 }
439}
440
441#[derive(Debug, Clone, PartialEq, Eq)]
442pub struct Navigation(String, Arc<ClientInfo>);
443
444impl From<RouteRequest> for Navigation {
445 fn from(value: RouteRequest) -> Self {
446 let mut query = String::new();
447
448 for (key, value) in &value.query {
449 if query.is_empty() {
450 query.push('?');
451 } else {
452 query.push('&');
453 }
454 query.push_str(key);
455 query.push('=');
456 query.push_str(value);
457 }
458
459 Self(format!("{}{query}", value.path), value.info.client)
460 }
461}
462
463impl From<&str> for RouteRequest {
464 fn from(value: &str) -> Self {
465 value.to_string().into()
466 }
467}
468
469impl From<String> for RouteRequest {
470 fn from(value: String) -> Self {
471 Self {
472 path: value,
473 method: Method::Get,
474 query: BTreeMap::new(),
475 headers: BTreeMap::new(),
476 cookies: BTreeMap::new(),
477 info: RequestInfo::default(),
478 body: None,
479 }
480 }
481}
482
483impl From<&String> for RouteRequest {
484 fn from(value: &String) -> Self {
485 value.to_string().into()
486 }
487}
488
489impl From<(&str, ClientInfo)> for RouteRequest {
490 fn from(value: (&str, ClientInfo)) -> Self {
491 (value.0.to_string(), Arc::new(value.1)).into()
492 }
493}
494
495impl From<(String, ClientInfo)> for RouteRequest {
496 fn from(value: (String, ClientInfo)) -> Self {
497 (value.0, Arc::new(value.1)).into()
498 }
499}
500
501impl From<(&String, ClientInfo)> for RouteRequest {
502 fn from(value: (&String, ClientInfo)) -> Self {
503 (value.0.to_string(), Arc::new(value.1)).into()
504 }
505}
506
507impl From<(&str, Arc<ClientInfo>)> for RouteRequest {
508 fn from(value: (&str, Arc<ClientInfo>)) -> Self {
509 (value.0.to_string(), value.1).into()
510 }
511}
512
513impl From<(String, Arc<ClientInfo>)> for RouteRequest {
514 fn from(value: (String, Arc<ClientInfo>)) -> Self {
515 (value.0, RequestInfo { client: value.1 }).into()
516 }
517}
518
519impl From<(&String, Arc<ClientInfo>)> for RouteRequest {
520 fn from(value: (&String, Arc<ClientInfo>)) -> Self {
521 (value.0.to_string(), value.1).into()
522 }
523}
524
525impl From<(&str, RequestInfo)> for RouteRequest {
526 fn from(value: (&str, RequestInfo)) -> Self {
527 (value.0.to_string(), value.1).into()
528 }
529}
530
531impl From<(String, RequestInfo)> for RouteRequest {
532 fn from(value: (String, RequestInfo)) -> Self {
533 let (path, query) = if let Some((path, query)) = value.0.split_once('?') {
534 (path.to_string(), query)
535 } else {
536 (value.0, "")
537 };
538
539 Self {
540 path,
541 method: Method::Get,
542 query: QString::from(query).into_iter().collect(),
543 headers: BTreeMap::new(),
544 cookies: BTreeMap::new(),
545 info: value.1,
546 body: None,
547 }
548 }
549}
550
551impl From<(&String, RequestInfo)> for RouteRequest {
552 fn from(value: (&String, RequestInfo)) -> Self {
553 (value.0.to_string(), value.1).into()
554 }
555}
556
557impl From<&RouteRequest> for Navigation {
558 fn from(value: &RouteRequest) -> Self {
559 value.clone().into()
560 }
561}
562
563impl From<&str> for Navigation {
564 fn from(value: &str) -> Self {
565 Self(value.to_string(), DEFAULT_CLIENT_INFO.clone())
566 }
567}
568
569impl From<String> for Navigation {
570 fn from(value: String) -> Self {
571 Self(value, DEFAULT_CLIENT_INFO.clone())
572 }
573}
574
575impl From<&String> for Navigation {
576 fn from(value: &String) -> Self {
577 Self(value.clone(), DEFAULT_CLIENT_INFO.clone())
578 }
579}
580
581impl From<(&str, ClientInfo)> for Navigation {
582 fn from(value: (&str, ClientInfo)) -> Self {
583 Self(value.0.to_string(), Arc::new(value.1))
584 }
585}
586
587impl From<(String, ClientInfo)> for Navigation {
588 fn from(value: (String, ClientInfo)) -> Self {
589 Self(value.0, Arc::new(value.1))
590 }
591}
592
593impl From<(&String, ClientInfo)> for Navigation {
594 fn from(value: (&String, ClientInfo)) -> Self {
595 Self(value.0.to_string(), Arc::new(value.1))
596 }
597}
598
599impl From<(&str, Arc<ClientInfo>)> for Navigation {
600 fn from(value: (&str, Arc<ClientInfo>)) -> Self {
601 Self(value.0.to_string(), value.1)
602 }
603}
604
605impl From<(String, Arc<ClientInfo>)> for Navigation {
606 fn from(value: (String, Arc<ClientInfo>)) -> Self {
607 Self(value.0, value.1)
608 }
609}
610
611impl From<(&String, Arc<ClientInfo>)> for Navigation {
612 fn from(value: (&String, Arc<ClientInfo>)) -> Self {
613 Self(value.0.to_string(), value.1)
614 }
615}
616
617impl From<(&str, RequestInfo)> for Navigation {
618 fn from(value: (&str, RequestInfo)) -> Self {
619 Self(value.0.to_string(), value.1.client)
620 }
621}
622
623impl From<(String, RequestInfo)> for Navigation {
624 fn from(value: (String, RequestInfo)) -> Self {
625 Self(value.0, value.1.client)
626 }
627}
628
629impl From<(&String, RequestInfo)> for Navigation {
630 fn from(value: (&String, RequestInfo)) -> Self {
631 Self(value.0.to_string(), value.1.client)
632 }
633}
634
635impl Router {
636 #[must_use]
637 pub fn new() -> Self {
638 let (tx, rx) = flume::unbounded();
639
640 Self {
641 #[cfg(feature = "static-routes")]
642 static_routes: Arc::new(RwLock::new(vec![])),
643 routes: Arc::new(RwLock::new(vec![])),
644 sender: tx,
645 receiver: rx,
646 }
647 }
648
649 #[must_use]
653 pub fn with_route_result<
654 C: TryInto<Content>,
655 Response: Into<Option<C>>,
656 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
657 BoxE: Into<Box<dyn std::error::Error>>,
658 >(
659 self,
660 route: impl Into<RoutePath>,
661 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
662 ) -> Self
663 where
664 C::Error: Into<Box<dyn std::error::Error>>,
665 {
666 self.routes
667 .write()
668 .unwrap()
669 .push((route.into(), gen_route_func_result(handler)));
670 self
671 }
672
673 #[must_use]
677 pub fn with_no_content_result<
678 F: Future<Output = Result<(), BoxE>> + Send + 'static,
679 BoxE: Into<Box<dyn std::error::Error>>,
680 >(
681 self,
682 route: impl Into<RoutePath>,
683 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
684 ) -> Self {
685 self.with_route_result::<Content, Option<Content>, _, _>(route, move |req: RouteRequest| {
686 let fut = handler(req);
687 async move { fut.await.map(|()| None::<Content>).map_err(Into::into) }
688 })
689 }
690
691 #[allow(clippy::needless_pass_by_value)]
695 #[must_use]
696 pub fn with_static_route_result<
697 C: TryInto<Content>,
698 Response: Into<Option<C>>,
699 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
700 BoxE: Into<Box<dyn std::error::Error>>,
701 >(
702 self,
703 #[allow(unused_variables)] route: impl Into<RoutePath>,
704 #[allow(unused_variables)] handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
705 ) -> Self
706 where
707 C::Error: Into<Box<dyn std::error::Error>>,
708 {
709 #[cfg(feature = "static-routes")]
710 self.static_routes
711 .write()
712 .unwrap()
713 .push((route.into(), gen_route_func_result(handler)));
714 self
715 }
716
717 #[must_use]
721 pub fn with_route<
722 C: TryInto<Content>,
723 Response: Into<Option<C>>,
724 F: Future<Output = Response> + Send + 'static,
725 >(
726 self,
727 route: impl Into<RoutePath>,
728 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
729 ) -> Self
730 where
731 C::Error: std::error::Error + 'static,
732 {
733 self.routes
734 .write()
735 .unwrap()
736 .push((route.into(), gen_route_func(handler)));
737 self
738 }
739
740 #[allow(clippy::needless_pass_by_value)]
744 #[must_use]
745 pub fn with_static_route<
746 C: TryInto<Content>,
747 Response: Into<Option<C>>,
748 F: Future<Output = Response> + Send + 'static,
749 >(
750 self,
751 #[allow(unused_variables)] route: impl Into<RoutePath>,
752 #[allow(unused_variables)] handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
753 ) -> Self
754 where
755 C::Error: std::error::Error + 'static,
756 {
757 #[cfg(feature = "static-routes")]
758 self.static_routes
759 .write()
760 .unwrap()
761 .push((route.into(), gen_route_func(handler)));
762 self
763 }
764
765 fn get_route_func(&self, path: &str) -> Option<RouteFunc> {
766 let dyn_route = self
767 .routes
768 .read()
769 .unwrap()
770 .iter()
771 .find(|(route, _)| route.matches(path))
772 .cloned()
773 .map(|(_, handler)| handler);
774
775 #[cfg(feature = "static-routes")]
776 if dyn_route.is_none() {
777 return self
778 .static_routes
779 .read()
780 .unwrap()
781 .iter()
782 .find(|(route, _)| route.matches(path))
783 .cloned()
784 .map(|(_, handler)| handler);
785 }
786
787 dyn_route
788 }
789
790 pub async fn navigate(
798 &self,
799 navigation: impl Into<RouteRequest>,
800 ) -> Result<Option<Content>, NavigateError> {
801 let req = navigation.into();
802
803 log::debug!("navigate: method={} path={}", req.method, req.path);
804
805 let handler = self.get_route_func(&req.path);
806
807 Ok(if let Some(handler) = handler {
808 match handler(req).await {
809 Ok(view) => view,
810 Err(e) => {
811 log::error!("Failed to fetch route view: {e:?}");
812 return Err(NavigateError::Handler(Box::new(std::io::Error::other(
813 e.to_string(),
814 ))));
815 }
816 }
817 } else {
818 log::warn!("Invalid navigation path={}", req.path);
819 return Err(NavigateError::InvalidPath);
820 })
821 }
822
823 pub async fn navigate_send(
831 &self,
832 navigation: impl Into<RouteRequest>,
833 ) -> Result<(), NavigateError> {
834 let req = navigation.into();
835
836 log::debug!("navigate_send: method={} path={}", req.method, req.path);
837
838 let view = {
839 let handler = self.get_route_func(&req.path);
840
841 if let Some(handler) = handler {
842 match handler(req).await {
843 Ok(view) => view,
844 Err(e) => {
845 log::error!("Failed to fetch route view: {e:?}");
846 return Err(NavigateError::Handler(Box::new(std::io::Error::other(
847 e.to_string(),
848 ))));
849 }
850 }
851 } else {
852 log::warn!("Invalid navigation path={}", req.path);
853 return Err(NavigateError::InvalidPath);
854 }
855 };
856
857 if let Some(view) = view {
858 self.sender.send(view).map_err(|e| {
859 log::error!("Failed to send: {e:?}");
860 NavigateError::Sender
861 })?;
862 }
863
864 Ok(())
865 }
866
867 #[must_use]
871 pub fn navigate_spawn(
872 &self,
873 navigation: impl Into<RouteRequest>,
874 ) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send>>> {
875 let navigation = navigation.into();
876
877 log::debug!("navigate_spawn: navigation={navigation:?}");
878
879 self.navigate_spawn_on(&tokio::runtime::Handle::current(), navigation)
880 }
881
882 #[must_use]
886 pub fn navigate_spawn_on(
887 &self,
888 handle: &tokio::runtime::Handle,
889 navigation: impl Into<RouteRequest>,
890 ) -> JoinHandle<Result<(), Box<dyn std::error::Error + Send>>> {
891 let navigation = navigation.into();
892
893 log::debug!("navigate_spawn_on: navigation={navigation:?}");
894
895 let router = self.clone();
896 moosicbox_task::spawn_on("NativeApp navigate_spawn", handle, async move {
897 router
898 .navigate_send(navigation)
899 .await
900 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)
901 })
902 }
903
904 #[must_use]
905 pub async fn wait_for_navigation(&self) -> Option<Content> {
906 self.receiver.recv_async().await.ok()
907 }
908}
909
910fn gen_route_func<
911 C: TryInto<Content>,
912 Response: Into<Option<C>>,
913 F: Future<Output = Response> + Send + 'static,
914>(
915 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
916) -> RouteFunc
917where
918 C::Error: std::error::Error + 'static,
919{
920 Arc::new(Box::new(move |req| {
921 Box::pin({
922 let handler = handler.clone();
923 async move {
924 let resp: Result<Option<Content>, Box<dyn std::error::Error>> = handler(req)
925 .await
926 .into()
927 .map(TryInto::try_into)
928 .transpose()
929 .map_err(|e| {
930 log::error!("Failed to handle route: {e:?}");
931 Box::new(e) as Box<dyn std::error::Error>
932 });
933 resp
934 }
935 })
936 }))
937}
938
939fn gen_route_func_result<
940 C: TryInto<Content>,
941 Response: Into<Option<C>>,
942 F: Future<Output = Result<Response, BoxE>> + Send + 'static,
943 BoxE: Into<Box<dyn std::error::Error>>,
944>(
945 handler: impl Fn(RouteRequest) -> F + Send + Sync + Clone + 'static,
946) -> RouteFunc
947where
948 C::Error: Into<Box<dyn std::error::Error>>,
949{
950 Arc::new(Box::new(move |req| {
951 Box::pin({
952 let handler = handler.clone();
953 async move {
954 let resp: Result<Response, Box<dyn std::error::Error>> =
955 handler(req).await.map_err(Into::into);
956 match resp.map(|x| {
957 let x: Result<Option<Content>, Box<dyn std::error::Error>> = x
958 .into()
959 .map(TryInto::try_into)
960 .transpose()
961 .map_err(Into::into);
962 x
963 }) {
964 Ok(x) => match x {
965 Ok(x) => Ok(x),
966 Err(e) => Err(e),
967 },
968 Err(e) => Err(e),
969 }
970 }
971 })
972 }))
973}