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