hyperchad_router/
lib.rs

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    /// # Errors
128    ///
129    /// * If the `Content-Type` header is missing
130    /// * If the form is missing
131    #[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                            // base64‑encode
193                            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    /// # Errors
232    ///
233    /// * If the `Content-Type` is not `application/json` or `application/x-www-form-urlencoded`
234    /// * If the body is missing
235    #[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    /// # Panics
650    ///
651    /// Will panic if routes `RwLock` is poisoned.
652    #[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    /// # Panics
674    ///
675    /// Will panic if routes `RwLock` is poisoned.
676    #[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    /// # Panics
692    ///
693    /// Will panic if routes `RwLock` is poisoned.
694    #[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    /// # Panics
718    ///
719    /// Will panic if routes `RwLock` is poisoned.
720    #[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    /// # Panics
741    ///
742    /// Will panic if routes `RwLock` is poisoned.
743    #[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    /// # Errors
791    ///
792    /// Will error if `Renderer` implementation fails to render the navigation result.
793    ///
794    /// # Panics
795    ///
796    /// Will panic if routes `RwLock` is poisoned.
797    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    /// # Errors
824    ///
825    /// Will error if `Renderer` implementation fails to render the navigation result.
826    ///
827    /// # Panics
828    ///
829    /// Will panic if routes `RwLock` is poisoned.
830    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    /// # Errors
868    ///
869    /// Will error if there was an error navigating
870    #[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    /// # Errors
883    ///
884    /// Will error if there was an error navigating
885    #[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}