dioxus_router/
routable.rs

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