dioxus_router/routable.rs
1#![allow(non_snake_case)]
2//! # Routable
3
4use dioxus_lib::core::Element;
5use std::iter::FlatMap;
6use std::slice::Iter;
7use std::{fmt::Display, str::FromStr};
8
9/// An error that occurs when parsing a route.
10#[derive(Debug, PartialEq)]
11pub struct RouteParseError<E: Display> {
12 /// The attempted routes that failed to match.
13 pub attempted_routes: Vec<E>,
14}
15
16impl<E: Display> Display for RouteParseError<E> {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "Route did not match:\nAttempted Matches:\n")?;
19 for (i, route) in self.attempted_routes.iter().enumerate() {
20 writeln!(f, "{}) {route}", i + 1)?;
21 }
22 Ok(())
23 }
24}
25
26/// Something that can be created from an entire query string. This trait must be implemented for any type that is spread into the query segment like `#[route("/?:..query")]`.
27///
28///
29/// **This trait is automatically implemented for any types that implement `From<&str>`.**
30///
31/// ```rust
32/// use dioxus::prelude::*;
33///
34/// #[derive(Routable, Clone, PartialEq, Debug)]
35/// enum Route {
36/// // FromQuery must be implemented for any types you spread into the query segment
37/// #[route("/?:..query")]
38/// Home {
39/// query: CustomQuery
40/// },
41/// }
42///
43/// #[derive(Default, Clone, PartialEq, Debug)]
44/// struct CustomQuery {
45/// count: i32,
46/// }
47///
48/// // We implement From<&str> for CustomQuery so that FromQuery is implemented automatically
49/// impl From<&str> for CustomQuery {
50/// fn from(query: &str) -> Self {
51/// CustomQuery {
52/// count: query.parse().unwrap_or(0),
53/// }
54/// }
55/// }
56///
57/// // We also need to implement Display for CustomQuery which will be used to format the query string into the URL
58/// impl std::fmt::Display for CustomQuery {
59/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60/// write!(f, "{}", self.count)
61/// }
62/// }
63///
64/// # #[component]
65/// # fn Home(query: CustomQuery) -> Element {
66/// # unimplemented!()
67/// # }
68/// ```
69#[rustversion::attr(
70 since(1.78.0),
71 diagnostic::on_unimplemented(
72 message = "`FromQuery` is not implemented for `{Self}`",
73 label = "spread query",
74 note = "FromQuery is automatically implemented for types that implement `From<&str>`. You need to either implement From<&str> or implement FromQuery manually."
75 )
76)]
77pub trait FromQuery {
78 /// Create an instance of `Self` from a query string.
79 fn from_query(query: &str) -> Self;
80}
81
82impl<T: for<'a> From<&'a str>> FromQuery for T {
83 fn from_query(query: &str) -> Self {
84 T::from(query)
85 }
86}
87
88/// Something that can be created from a query argument. This trait must be implemented for any type that is used as a query argument like `#[route("/?:query")]`.
89///
90/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
91///
92/// ```rust
93/// use dioxus::prelude::*;
94///
95/// #[derive(Routable, Clone, PartialEq, Debug)]
96/// enum Route {
97/// // FromQuerySegment must be implemented for any types you use in the query segment
98/// // When you don't spread the query, you can parse multiple values form the query
99/// // This url will be in the format `/?query=123&other=456`
100/// #[route("/?:query&:other")]
101/// Home {
102/// query: CustomQuery,
103/// other: i32,
104/// },
105/// }
106///
107/// // We can derive Default for CustomQuery
108/// // If the router fails to parse the query value, it will use the default value instead
109/// #[derive(Default, Clone, PartialEq, Debug)]
110/// struct CustomQuery {
111/// count: i32,
112/// }
113///
114/// // We implement FromStr for CustomQuery so that FromQuerySegment is implemented automatically
115/// impl std::str::FromStr for CustomQuery {
116/// type Err = <i32 as std::str::FromStr>::Err;
117///
118/// fn from_str(query: &str) -> Result<Self, Self::Err> {
119/// Ok(CustomQuery {
120/// count: query.parse()?,
121/// })
122/// }
123/// }
124///
125/// // We also need to implement Display for CustomQuery so that ToQueryArgument is implemented automatically
126/// impl std::fmt::Display for CustomQuery {
127/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128/// write!(f, "{}", self.count)
129/// }
130/// }
131///
132/// # #[component]
133/// # fn Home(query: CustomQuery, other: i32) -> Element {
134/// # unimplemented!()
135/// # }
136/// ```
137#[rustversion::attr(
138 since(1.78.0),
139 diagnostic::on_unimplemented(
140 message = "`FromQueryArgument` is not implemented for `{Self}`",
141 label = "query argument",
142 note = "FromQueryArgument is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromQueryArgument manually."
143 )
144)]
145pub trait FromQueryArgument<P = ()>: Default {
146 /// The error that can occur when parsing a query argument.
147 type Err;
148
149 /// Create an instance of `Self` from a query string.
150 fn from_query_argument(argument: &str) -> Result<Self, Self::Err>;
151}
152
153impl<T: Default + FromStr> FromQueryArgument for T
154where
155 <T as FromStr>::Err: Display,
156{
157 type Err = <T as FromStr>::Err;
158
159 fn from_query_argument(argument: &str) -> Result<Self, Self::Err> {
160 match T::from_str(argument) {
161 Ok(result) => Ok(result),
162 Err(err) => {
163 tracing::error!("Failed to parse query argument: {}", err);
164 Err(err)
165 }
166 }
167 }
168}
169
170/// A marker type for `Option<T>` to implement `FromQueryArgument`.
171pub struct OptionMarker;
172
173impl<T: Default + FromStr> FromQueryArgument<OptionMarker> for Option<T>
174where
175 <T as FromStr>::Err: Display,
176{
177 type Err = <T as FromStr>::Err;
178
179 fn from_query_argument(argument: &str) -> Result<Self, Self::Err> {
180 match T::from_str(argument) {
181 Ok(result) => Ok(Some(result)),
182 Err(err) => {
183 tracing::error!("Failed to parse query argument: {}", err);
184 Err(err)
185 }
186 }
187 }
188}
189
190/// Something that can be formatted as a query argument. This trait must be implemented for any type that is used as a query argument like `#[route("/?:query")]`.
191///
192/// **This trait is automatically implemented for any types that implement [`Display`].**
193///
194/// ```rust
195/// use dioxus::prelude::*;
196///
197/// #[derive(Routable, Clone, PartialEq, Debug)]
198/// enum Route {
199/// // FromQuerySegment must be implemented for any types you use in the query segment
200/// // When you don't spread the query, you can parse multiple values form the query
201/// // This url will be in the format `/?query=123&other=456`
202/// #[route("/?:query&:other")]
203/// Home {
204/// query: CustomQuery,
205/// other: i32,
206/// },
207/// }
208///
209/// // We can derive Default for CustomQuery
210/// // If the router fails to parse the query value, it will use the default value instead
211/// #[derive(Default, Clone, PartialEq, Debug)]
212/// struct CustomQuery {
213/// count: i32,
214/// }
215///
216/// // We implement FromStr for CustomQuery so that FromQuerySegment is implemented automatically
217/// impl std::str::FromStr for CustomQuery {
218/// type Err = <i32 as std::str::FromStr>::Err;
219///
220/// fn from_str(query: &str) -> Result<Self, Self::Err> {
221/// Ok(CustomQuery {
222/// count: query.parse()?,
223/// })
224/// }
225/// }
226///
227/// // We also need to implement Display for CustomQuery so that ToQueryArgument is implemented automatically
228/// impl std::fmt::Display for CustomQuery {
229/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230/// write!(f, "{}", self.count)
231/// }
232/// }
233///
234/// # #[component]
235/// # fn Home(query: CustomQuery, other: i32) -> Element {
236/// # unimplemented!()
237/// # }
238/// ```
239pub trait ToQueryArgument<T = ()> {
240 /// Display the query argument as a string.
241 fn display_query_argument(
242 &self,
243 query_name: &str,
244 f: &mut std::fmt::Formatter<'_>,
245 ) -> std::fmt::Result;
246}
247
248impl<T> ToQueryArgument for T
249where
250 T: Display,
251{
252 fn display_query_argument(
253 &self,
254 query_name: &str,
255 f: &mut std::fmt::Formatter<'_>,
256 ) -> std::fmt::Result {
257 write!(f, "{}={}", query_name, self)
258 }
259}
260
261impl<T: Display> ToQueryArgument<OptionMarker> for Option<T> {
262 fn display_query_argument(
263 &self,
264 query_name: &str,
265 f: &mut std::fmt::Formatter<'_>,
266 ) -> std::fmt::Result {
267 if let Some(value) = self {
268 write!(f, "{}={}", query_name, value)
269 } else {
270 Ok(())
271 }
272 }
273}
274
275/// A type that implements [`ToQueryArgument`] along with the query name. This type implements Display and can be used to format the query argument into a string.
276pub struct DisplayQueryArgument<'a, T, M = ()> {
277 /// The query name.
278 query_name: &'a str,
279 /// The value to format.
280 value: &'a T,
281 /// The `ToQueryArgument` marker type, which can be used to differentiate between different types of query arguments.
282 _marker: std::marker::PhantomData<M>,
283}
284
285impl<'a, T, M> DisplayQueryArgument<'a, T, M> {
286 /// Create a new `DisplayQueryArgument`.
287 pub fn new(query_name: &'a str, value: &'a T) -> Self {
288 Self {
289 query_name,
290 value,
291 _marker: std::marker::PhantomData,
292 }
293 }
294}
295
296impl<T: ToQueryArgument<M>, M> Display for DisplayQueryArgument<'_, T, M> {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 self.value.display_query_argument(self.query_name, f)
299 }
300}
301
302/// Something that can be created from an entire hash fragment. This must be implemented for any type that is used as a hash fragment like `#[route("/#:hash_fragment")]`.
303///
304///
305/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
306///
307/// # Example
308///
309/// ```rust
310/// use dioxus::prelude::*;
311///
312/// #[derive(Routable, Clone)]
313/// #[rustfmt::skip]
314/// enum Route {
315/// // State is stored in the url hash
316/// #[route("/#:url_hash")]
317/// Home {
318/// url_hash: State,
319/// },
320/// }
321///
322/// #[component]
323/// fn Home(url_hash: State) -> Element {
324/// unimplemented!()
325/// }
326///
327///
328/// #[derive(Clone, PartialEq, Default)]
329/// struct State {
330/// count: usize,
331/// other_count: usize
332/// }
333///
334/// // The hash segment will be displayed as a string (this will be url encoded automatically)
335/// impl std::fmt::Display for State {
336/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337/// write!(f, "{}-{}", self.count, self.other_count)
338/// }
339/// }
340///
341/// // We need to parse the hash fragment into a struct from the string (this will be url decoded automatically)
342/// impl FromHashFragment for State {
343/// fn from_hash_fragment(hash: &str) -> Self {
344/// let Some((first, second)) = hash.split_once('-') else {
345/// // URL fragment parsing shouldn't fail. You can return a default value if you want
346/// return Default::default();
347/// };
348///
349/// let first = first.parse().unwrap();
350/// let second = second.parse().unwrap();
351///
352/// State {
353/// count: first,
354/// other_count: second,
355/// }
356/// }
357/// }
358/// ```
359#[rustversion::attr(
360 since(1.78.0),
361 diagnostic::on_unimplemented(
362 message = "`FromHashFragment` is not implemented for `{Self}`",
363 label = "hash fragment",
364 note = "FromHashFragment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromHashFragment manually."
365 )
366)]
367pub trait FromHashFragment {
368 /// Create an instance of `Self` from a hash fragment.
369 fn from_hash_fragment(hash: &str) -> Self;
370}
371
372impl<T> FromHashFragment for T
373where
374 T: FromStr + Default,
375 T::Err: std::fmt::Display,
376{
377 fn from_hash_fragment(hash: &str) -> Self {
378 match T::from_str(hash) {
379 Ok(value) => value,
380 Err(err) => {
381 tracing::error!("Failed to parse hash fragment: {}", err);
382 Default::default()
383 }
384 }
385 }
386}
387
388/// Something that can be created from a single route segment. This must be implemented for any type that is used as a route segment like `#[route("/:route_segment")]`.
389///
390///
391/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
392///
393/// ```rust
394/// use dioxus::prelude::*;
395///
396/// #[derive(Routable, Clone, PartialEq, Debug)]
397/// enum Route {
398/// // FromRouteSegment must be implemented for any types you use in the route segment
399/// // When you don't spread the route, you can parse multiple values from the route
400/// // This url will be in the format `/123/456`
401/// #[route("/:route_segment_one/:route_segment_two")]
402/// Home {
403/// route_segment_one: CustomRouteSegment,
404/// route_segment_two: i32,
405/// },
406/// }
407///
408/// // We can derive Default for CustomRouteSegment
409/// // If the router fails to parse the route segment, it will use the default value instead
410/// #[derive(Default, PartialEq, Clone, Debug)]
411/// struct CustomRouteSegment {
412/// count: i32,
413/// }
414///
415/// // We implement FromStr for CustomRouteSegment so that FromRouteSegment is implemented automatically
416/// impl std::str::FromStr for CustomRouteSegment {
417/// type Err = <i32 as std::str::FromStr>::Err;
418///
419/// fn from_str(route_segment: &str) -> Result<Self, Self::Err> {
420/// Ok(CustomRouteSegment {
421/// count: route_segment.parse()?,
422/// })
423/// }
424/// }
425///
426/// // We also need to implement Display for CustomRouteSegment which will be used to format the route segment into the URL
427/// impl std::fmt::Display for CustomRouteSegment {
428/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429/// write!(f, "{}", self.count)
430/// }
431/// }
432///
433/// # #[component]
434/// # fn Home(route_segment_one: CustomRouteSegment, route_segment_two: i32) -> Element {
435/// # unimplemented!()
436/// # }
437/// ```
438#[rustversion::attr(
439 since(1.78.0),
440 diagnostic::on_unimplemented(
441 message = "`FromRouteSegment` is not implemented for `{Self}`",
442 label = "route segment",
443 note = "FromRouteSegment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromRouteSegment manually."
444 )
445)]
446pub trait FromRouteSegment: Sized {
447 /// The error that can occur when parsing a route segment.
448 type Err;
449
450 /// Create an instance of `Self` from a route segment.
451 fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
452}
453
454impl<T: FromStr> FromRouteSegment for T
455where
456 <T as FromStr>::Err: Display,
457{
458 type Err = <T as FromStr>::Err;
459
460 fn from_route_segment(route: &str) -> Result<Self, Self::Err> {
461 T::from_str(route)
462 }
463}
464
465#[test]
466fn full_circle() {
467 let route = "testing 1234 hello world";
468 assert_eq!(String::from_route_segment(route).unwrap(), route);
469}
470
471/// Something that can be converted into multiple route segments. This must be implemented for any type that is spread into the route segment like `#[route("/:..route_segments")]`.
472///
473///
474/// **This trait is automatically implemented for any types that implement `IntoIterator<Item=impl Display>`.**
475///
476/// ```rust
477/// use dioxus::prelude::*;
478///
479/// #[derive(Routable, Clone, PartialEq, Debug)]
480/// enum Route {
481/// // FromRouteSegments must be implemented for any types you use in the route segment
482/// // When you spread the route, you can parse multiple values from the route
483/// // This url will be in the format `/123/456/789`
484/// #[route("/:..numeric_route_segments")]
485/// Home {
486/// numeric_route_segments: NumericRouteSegments,
487/// },
488/// }
489///
490/// // We can derive Default for NumericRouteSegments
491/// // If the router fails to parse the route segment, it will use the default value instead
492/// #[derive(Default, PartialEq, Clone, Debug)]
493/// struct NumericRouteSegments {
494/// numbers: Vec<i32>,
495/// }
496///
497/// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments
498/// impl ToRouteSegments for NumericRouteSegments {
499/// fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500/// for number in &self.numbers {
501/// write!(f, "/{}", number)?;
502/// }
503/// Ok(())
504/// }
505/// }
506///
507/// // We also need to parse the route segments with `FromRouteSegments`
508/// impl FromRouteSegments for NumericRouteSegments {
509/// type Err = <i32 as std::str::FromStr>::Err;
510///
511/// fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
512/// let mut numbers = Vec::new();
513/// for segment in segments {
514/// numbers.push(segment.parse()?);
515/// }
516/// Ok(NumericRouteSegments { numbers })
517/// }
518/// }
519///
520/// # #[component]
521/// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {
522/// # unimplemented!()
523/// # }
524/// ```
525pub trait ToRouteSegments {
526 /// Display the route segments with each route segment separated by a `/`. This should not start with a `/`.
527 ///
528 fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
529}
530
531// Implement ToRouteSegments for any type that can turn &self into an iterator of &T where T: Display
532impl<I, T: Display> ToRouteSegments for I
533where
534 for<'a> &'a I: IntoIterator<Item = &'a T>,
535{
536 fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537 for segment in self {
538 write!(f, "/")?;
539 let segment = segment.to_string();
540 let encoded =
541 percent_encoding::utf8_percent_encode(&segment, crate::query_sets::PATH_ASCII_SET);
542 write!(f, "{}", encoded)?;
543 }
544 Ok(())
545 }
546}
547
548#[test]
549fn to_route_segments() {
550 struct DisplaysRoute;
551
552 impl Display for DisplaysRoute {
553 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554 let segments = vec!["hello", "world"];
555 segments.display_route_segments(f)
556 }
557 }
558
559 assert_eq!(DisplaysRoute.to_string(), "/hello/world");
560}
561
562/// Something that can be created from multiple route segments. This must be implemented for any type that is spread into the route segment like `#[route("/:..route_segments")]`.
563///
564///
565/// **This trait is automatically implemented for any types that implement `FromIterator<impl Display>`.**
566///
567/// ```rust
568/// use dioxus::prelude::*;
569///
570/// #[derive(Routable, Clone, PartialEq, Debug)]
571/// enum Route {
572/// // FromRouteSegments must be implemented for any types you use in the route segment
573/// // When you spread the route, you can parse multiple values from the route
574/// // This url will be in the format `/123/456/789`
575/// #[route("/:..numeric_route_segments")]
576/// Home {
577/// numeric_route_segments: NumericRouteSegments,
578/// },
579/// }
580///
581/// // We can derive Default for NumericRouteSegments
582/// // If the router fails to parse the route segment, it will use the default value instead
583/// #[derive(Default, Clone, PartialEq, Debug)]
584/// struct NumericRouteSegments {
585/// numbers: Vec<i32>,
586/// }
587///
588/// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments
589/// impl ToRouteSegments for NumericRouteSegments {
590/// fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
591/// for number in &self.numbers {
592/// write!(f, "/{}", number)?;
593/// }
594/// Ok(())
595/// }
596/// }
597///
598/// // We also need to parse the route segments with `FromRouteSegments`
599/// impl FromRouteSegments for NumericRouteSegments {
600/// type Err = <i32 as std::str::FromStr>::Err;
601///
602/// fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
603/// let mut numbers = Vec::new();
604/// for segment in segments {
605/// numbers.push(segment.parse()?);
606/// }
607/// Ok(NumericRouteSegments { numbers })
608/// }
609/// }
610///
611/// # #[component]
612/// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {
613/// # unimplemented!()
614/// # }
615/// ```
616#[rustversion::attr(
617 since(1.78.0),
618 diagnostic::on_unimplemented(
619 message = "`FromRouteSegments` is not implemented for `{Self}`",
620 label = "spread route segments",
621 note = "FromRouteSegments is automatically implemented for types that implement `FromIterator` with an `Item` type that implements `Display`. You need to either implement FromIterator or implement FromRouteSegments manually."
622 )
623)]
624pub trait FromRouteSegments: Sized {
625 /// The error that can occur when parsing route segments.
626 type Err: std::fmt::Display;
627
628 /// Create an instance of `Self` from route segments.
629 ///
630 /// NOTE: This method must parse the output of `ToRouteSegments::display_route_segments` into the type `Self`.
631 fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
632}
633
634impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
635 type Err = <String as FromRouteSegment>::Err;
636
637 fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
638 segments
639 .iter()
640 .map(|s| String::from_route_segment(s))
641 .collect()
642 }
643}
644
645/// A flattened version of [`Routable::SITE_MAP`].
646/// This essentially represents a `Vec<Vec<SegmentType>>`, which you can collect it into.
647type SiteMapFlattened<'a> = FlatMap<
648 Iter<'a, SiteMapSegment>,
649 Vec<Vec<SegmentType>>,
650 fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
651>;
652
653/// The Routable trait is implemented for types that can be converted to and from a route and be rendered as a page.
654///
655/// A Routable object is something that can be:
656/// 1. Converted from a route.
657/// 2. Converted to a route.
658/// 3. Rendered as a component.
659///
660/// This trait should generally be derived using the [`dioxus_router_macro::Routable`] macro which has more information about the syntax.
661///
662/// ## Example
663/// ```rust
664/// use dioxus::prelude::*;
665///
666/// fn App() -> Element {
667/// rsx! {
668/// Router::<Route> { }
669/// }
670/// }
671///
672/// // Routes are generally enums that derive `Routable`
673/// #[derive(Routable, Clone, PartialEq, Debug)]
674/// enum Route {
675/// // Each enum has an associated url
676/// #[route("/")]
677/// Home {},
678/// // Routes can include dynamic segments that are parsed from the url
679/// #[route("/blog/:blog_id")]
680/// Blog { blog_id: usize },
681/// // Or query segments that are parsed from the url
682/// #[route("/edit?:blog_id")]
683/// Edit { blog_id: usize },
684/// // Or hash segments that are parsed from the url
685/// #[route("/hashtag/#:hash")]
686/// Hash { hash: String },
687/// }
688///
689/// // Each route variant defaults to rendering a component of the same name
690/// #[component]
691/// fn Home() -> Element {
692/// rsx! {
693/// h1 { "Home" }
694/// }
695/// }
696///
697/// // If the variant has dynamic parameters, those are passed to the component
698/// #[component]
699/// fn Blog(blog_id: usize) -> Element {
700/// rsx! {
701/// h1 { "Blog" }
702/// }
703/// }
704///
705/// #[component]
706/// fn Edit(blog_id: usize) -> Element {
707/// rsx! {
708/// h1 { "Edit" }
709/// }
710/// }
711///
712/// #[component]
713/// fn Hash(hash: String) -> Element {
714/// rsx! {
715/// h1 { "Hashtag #{hash}" }
716/// }
717/// }
718/// ```
719#[rustversion::attr(
720 since(1.78.0),
721 diagnostic::on_unimplemented(
722 message = "`Routable` is not implemented for `{Self}`",
723 label = "Route",
724 note = "Routable should generally be derived using the `#[derive(Routable)]` macro."
725 )
726)]
727pub trait Routable: FromStr<Err: Display> + Display + Clone + 'static {
728 /// The error that can occur when parsing a route.
729 const SITE_MAP: &'static [SiteMapSegment];
730
731 /// Render the route at the given level
732 fn render(&self, level: usize) -> Element;
733
734 /// Checks if this route is a child of the given route.
735 ///
736 /// # Example
737 /// ```rust
738 /// use dioxus_router::prelude::*;
739 /// use dioxus::prelude::*;
740 ///
741 /// #[component]
742 /// fn Home() -> Element { VNode::empty() }
743 /// #[component]
744 /// fn About() -> Element { VNode::empty() }
745 ///
746 /// #[derive(Routable, Clone, PartialEq, Debug)]
747 /// enum Route {
748 /// #[route("/")]
749 /// Home {},
750 /// #[route("/about")]
751 /// About {},
752 /// }
753 ///
754 /// let route = Route::About {};
755 /// let parent = Route::Home {};
756 /// assert!(route.is_child_of(&parent));
757 /// ```
758 fn is_child_of(&self, other: &Self) -> bool {
759 let self_str = self.to_string();
760 let self_str = self_str
761 .split_once('#')
762 .map(|(route, _)| route)
763 .unwrap_or(&self_str);
764 let self_str = self_str
765 .split_once('?')
766 .map(|(route, _)| route)
767 .unwrap_or(self_str);
768 let self_str = self_str.trim_end_matches('/');
769 let other_str = other.to_string();
770 let other_str = other_str
771 .split_once('#')
772 .map(|(route, _)| route)
773 .unwrap_or(&other_str);
774 let other_str = other_str
775 .split_once('?')
776 .map(|(route, _)| route)
777 .unwrap_or(other_str);
778 let other_str = other_str.trim_end_matches('/');
779
780 let mut self_segments = self_str.split('/');
781 let mut other_segments = other_str.split('/');
782 loop {
783 match (self_segments.next(), other_segments.next()) {
784 // If the two routes are the same length, or this route has less segments, then this segment
785 // cannot be the child of the other segment
786 (None, Some(_)) | (None, None) => {
787 return false;
788 }
789 // If two segments are not the same, then this segment cannot be the child of the other segment
790 (Some(self_seg), Some(other_seg)) => {
791 if self_seg != other_seg {
792 return false;
793 }
794 }
795 // If the other route has less segments, then this route is the child of the other route
796 (Some(_), None) => break,
797 }
798 }
799 true
800 }
801
802 /// Get the parent route of this route.
803 ///
804 /// # Example
805 /// ```rust
806 /// use dioxus_router::prelude::*;
807 /// use dioxus::prelude::*;
808 ///
809 /// #[component]
810 /// fn Home() -> Element { VNode::empty() }
811 /// #[component]
812 /// fn About() -> Element { VNode::empty() }
813 ///
814 /// #[derive(Routable, Clone, PartialEq, Debug)]
815 /// enum Route {
816 /// #[route("/home")]
817 /// Home {},
818 /// #[route("/home/about")]
819 /// About {},
820 /// }
821 ///
822 /// let route = Route::About {};
823 /// let parent = route.parent().unwrap();
824 /// assert_eq!(parent, Route::Home {});
825 /// ```
826 fn parent(&self) -> Option<Self> {
827 let as_str = self.to_string();
828 let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, ""));
829 let (route, _) = route_and_query
830 .split_once('?')
831 .unwrap_or((route_and_query, ""));
832 let route = route.trim_end_matches('/');
833 let segments = route.split_inclusive('/');
834 let segment_count = segments.clone().count();
835 let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();
836 Self::from_str(&new_route).ok()
837 }
838
839 /// Returns a flattened version of [`Self::SITE_MAP`].
840 fn flatten_site_map<'a>() -> SiteMapFlattened<'a> {
841 Self::SITE_MAP.iter().flat_map(SiteMapSegment::flatten)
842 }
843
844 /// Gets a list of all the static routes.
845 /// Example static route: `#[route("/static/route")]`
846 fn static_routes() -> Vec<Self> {
847 Self::flatten_site_map()
848 .filter_map(|segments| {
849 let mut route = String::new();
850 for segment in segments.iter() {
851 match segment {
852 SegmentType::Static(s) => {
853 route.push('/');
854 route.push_str(s)
855 }
856 SegmentType::Child => {}
857 _ => return None,
858 }
859 }
860
861 route.parse().ok()
862 })
863 .collect()
864 }
865}
866
867/// A type erased map of the site structure.
868#[derive(Debug, Clone, PartialEq)]
869pub struct SiteMapSegment {
870 /// The type of the route segment.
871 pub segment_type: SegmentType,
872 /// The children of the route segment.
873 pub children: &'static [SiteMapSegment],
874}
875
876impl SiteMapSegment {
877 /// Take a map of the site structure and flatten it into a vector of routes.
878 pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
879 let mut routes = Vec::new();
880 self.flatten_inner(&mut routes, Vec::new());
881 routes
882 }
883
884 fn flatten_inner(&self, routes: &mut Vec<Vec<SegmentType>>, current: Vec<SegmentType>) {
885 let mut current = current;
886 current.push(self.segment_type.clone());
887 if self.children.is_empty() {
888 routes.push(current);
889 } else {
890 for child in self.children {
891 child.flatten_inner(routes, current.clone());
892 }
893 }
894 }
895}
896
897/// The type of a route segment.
898#[derive(Debug, Clone, PartialEq)]
899#[non_exhaustive]
900pub enum SegmentType {
901 /// A static route segment.
902 Static(&'static str),
903 /// A dynamic route segment.
904 Dynamic(&'static str),
905 /// A catch all route segment.
906 CatchAll(&'static str),
907 /// A child router.
908 Child,
909}
910
911impl SegmentType {
912 /// Try to convert this segment into a static segment.
913 pub fn to_static(&self) -> Option<&'static str> {
914 match self {
915 SegmentType::Static(s) => Some(*s),
916 _ => None,
917 }
918 }
919
920 /// Try to convert this segment into a dynamic segment.
921 pub fn to_dynamic(&self) -> Option<&'static str> {
922 match self {
923 SegmentType::Dynamic(s) => Some(*s),
924 _ => None,
925 }
926 }
927
928 /// Try to convert this segment into a catch all segment.
929 pub fn to_catch_all(&self) -> Option<&'static str> {
930 match self {
931 SegmentType::CatchAll(s) => Some(*s),
932 _ => None,
933 }
934 }
935
936 /// Try to convert this segment into a child segment.
937 pub fn to_child(&self) -> Option<()> {
938 match self {
939 SegmentType::Child => Some(()),
940 _ => None,
941 }
942 }
943}
944
945impl Display for SegmentType {
946 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
947 match &self {
948 SegmentType::Static(s) => write!(f, "/{}", s),
949 SegmentType::Child => Ok(()),
950 SegmentType::Dynamic(s) => write!(f, "/:{}", s),
951 SegmentType::CatchAll(s) => write!(f, "/:..{}", s),
952 }
953 }
954}