Skip to main content

dioxus_router/
routable.rs

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