dioxus_router/
navigation.rs

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