freya_router/
navigation.rs

1//! Types pertaining to navigation.
2
3use std::{
4    fmt::{
5        Debug,
6        Display,
7    },
8    str::FromStr,
9};
10
11use url::{
12    ParseError,
13    Url,
14};
15
16use crate::{
17    components::child_router::consume_child_route_mapping,
18    prelude::RouterContext,
19    routable::Routable,
20};
21
22impl<R: Routable> From<R> for NavigationTarget {
23    fn from(value: R) -> Self {
24        // If this is a child route, map it to the root route first
25        let mapping = consume_child_route_mapping();
26        match mapping.as_ref() {
27            Some(mapping) => NavigationTarget::Internal(mapping.format_route_as_root_route(value)),
28            // Otherwise, just use the internal route
29            None => NavigationTarget::Internal(value.to_string()),
30        }
31    }
32}
33
34/// A target for the router to navigate to.
35#[derive(Clone, PartialEq, Eq, Debug)]
36pub enum NavigationTarget<R = String> {
37    /// An internal path that the router can navigate to by itself.
38    ///
39    /// ```rust
40    /// # use freya::prelude::*;
41    /// # use freya_router::prelude::*;
42    /// # #[derive(PartialEq)]
43    /// # struct Index;
44    /// # impl Render for Index {
45    /// #    fn render(&self) -> impl IntoElement {
46    /// #        rect()
47    /// #    }
48    /// # }
49    /// #[derive(Clone, Routable, PartialEq, Debug)]
50    /// enum Route {
51    ///     #[route("/")]
52    ///     Index {},
53    /// }
54    /// let explicit = NavigationTarget::Internal(Route::Index {});
55    /// let implicit: NavigationTarget<Route> = "/".parse().unwrap();
56    /// assert_eq!(explicit, implicit);
57    /// ```
58    Internal(R),
59    /// An external target that the router doesn't control.
60    External(String),
61}
62
63impl<R: Routable> From<&str> for NavigationTarget<R> {
64    fn from(value: &str) -> Self {
65        value
66            .parse()
67            .unwrap_or_else(|_| Self::External(value.to_string()))
68    }
69}
70
71impl<R: Routable> From<&String> for NavigationTarget<R> {
72    fn from(value: &String) -> Self {
73        value.as_str().into()
74    }
75}
76
77impl<R: Routable> From<String> for NavigationTarget<R> {
78    fn from(value: String) -> Self {
79        value.as_str().into()
80    }
81}
82
83impl<R: Routable> From<R> for NavigationTarget<R> {
84    fn from(value: R) -> Self {
85        Self::Internal(value)
86    }
87}
88
89impl From<&str> for NavigationTarget {
90    fn from(value: &str) -> Self {
91        match RouterContext::try_get() {
92            Some(router) => match router.internal_route(value) {
93                true => NavigationTarget::Internal(value.to_string()),
94                false => NavigationTarget::External(value.to_string()),
95            },
96            None => NavigationTarget::External(value.to_string()),
97        }
98    }
99}
100
101impl From<String> for NavigationTarget {
102    fn from(value: String) -> Self {
103        match RouterContext::try_get() {
104            Some(router) => match router.internal_route(&value) {
105                true => NavigationTarget::Internal(value),
106                false => NavigationTarget::External(value),
107            },
108            None => NavigationTarget::External(value.to_string()),
109        }
110    }
111}
112
113impl<R: Routable> From<NavigationTarget<R>> for NavigationTarget {
114    fn from(value: NavigationTarget<R>) -> Self {
115        match value {
116            NavigationTarget::Internal(r) => r.into(),
117            NavigationTarget::External(s) => Self::External(s),
118        }
119    }
120}
121
122impl<R: Routable> Display for NavigationTarget<R> {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        match self {
125            NavigationTarget::Internal(r) => write!(f, "{r}"),
126            NavigationTarget::External(s) => write!(f, "{s}"),
127        }
128    }
129}
130
131/// An error that can occur when parsing a [`NavigationTarget`].
132pub enum NavigationTargetParseError<R: Routable> {
133    /// A URL that is not valid.
134    InvalidUrl(ParseError),
135    /// An internal URL that is not valid.
136    InvalidInternalURL(<R as FromStr>::Err),
137}
138
139impl<R: Routable> Debug for NavigationTargetParseError<R> {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        match self {
142            NavigationTargetParseError::InvalidUrl(e) => write!(f, "Invalid URL: {e}"),
143            NavigationTargetParseError::InvalidInternalURL(_) => {
144                write!(f, "Invalid internal URL")
145            }
146        }
147    }
148}
149
150impl<R: Routable> FromStr for NavigationTarget<R> {
151    type Err = NavigationTargetParseError<R>;
152
153    fn from_str(s: &str) -> Result<Self, Self::Err> {
154        match Url::parse(s) {
155            Ok(_) => Ok(Self::External(s.to_string())),
156            Err(ParseError::RelativeUrlWithoutBase) => {
157                Ok(Self::Internal(R::from_str(s).map_err(|e| {
158                    NavigationTargetParseError::InvalidInternalURL(e)
159                })?))
160            }
161            Err(e) => Err(NavigationTargetParseError::InvalidUrl(e)),
162        }
163    }
164}