123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822 |
- //! # Routable
- #![allow(non_snake_case)]
- use dioxus_lib::prelude::*;
- use std::iter::FlatMap;
- use std::slice::Iter;
- use std::{fmt::Display, str::FromStr};
- /// An error that occurs when parsing a route.
- #[derive(Debug, PartialEq)]
- pub struct RouteParseError<E: Display> {
- /// The attempted routes that failed to match.
- pub attempted_routes: Vec<E>,
- }
- impl<E: Display> Display for RouteParseError<E> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "Route did not match:\nAttempted Matches:\n")?;
- for (i, route) in self.attempted_routes.iter().enumerate() {
- writeln!(f, "{}) {route}", i + 1)?;
- }
- Ok(())
- }
- }
- /// 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")]`.
- ///
- ///
- /// **This trait is automatically implemented for any types that implement `From<&str>`.**
- ///
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// // FromQuery must be implemented for any types you spread into the query segment
- /// #[route("/?:..query")]
- /// Home {
- /// query: CustomQuery
- /// },
- /// }
- ///
- /// #[derive(Default, Clone, PartialEq, Debug)]
- /// struct CustomQuery {
- /// count: i32,
- /// }
- ///
- /// // We implement From<&str> for CustomQuery so that FromQuery is implemented automatically
- /// impl From<&str> for CustomQuery {
- /// fn from(query: &str) -> Self {
- /// CustomQuery {
- /// count: query.parse().unwrap_or(0),
- /// }
- /// }
- /// }
- ///
- /// // We also need to implement Display for CustomQuery which will be used to format the query string into the URL
- /// impl std::fmt::Display for CustomQuery {
- /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// write!(f, "{}", self.count)
- /// }
- /// }
- ///
- /// # #[component]
- /// # fn Home(query: CustomQuery) -> Element {
- /// # unimplemented!()
- /// # }
- /// ```
- #[rustversion::attr(
- since(1.78.0),
- diagnostic::on_unimplemented(
- message = "`FromQuery` is not implemented for `{Self}`",
- label = "spread query",
- note = "FromQuery is automatically implemented for types that implement `From<&str>`. You need to either implement From<&str> or implement FromQuery manually."
- )
- )]
- pub trait FromQuery {
- /// Create an instance of `Self` from a query string.
- fn from_query(query: &str) -> Self;
- }
- impl<T: for<'a> From<&'a str>> FromQuery for T {
- fn from_query(query: &str) -> Self {
- T::from(query)
- }
- }
- /// 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")]`.
- ///
- /// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
- ///
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// // FromQuerySegment must be implemented for any types you use in the query segment
- /// // When you don't spread the query, you can parse multiple values form the query
- /// // This url will be in the format `/?query=123&other=456`
- /// #[route("/?:query&:other")]
- /// Home {
- /// query: CustomQuery,
- /// other: i32,
- /// },
- /// }
- ///
- /// // We can derive Default for CustomQuery
- /// // If the router fails to parse the query value, it will use the default value instead
- /// #[derive(Default, Clone, PartialEq, Debug)]
- /// struct CustomQuery {
- /// count: i32,
- /// }
- ///
- /// // We implement FromStr for CustomQuery so that FromQuerySegment is implemented automatically
- /// impl std::str::FromStr for CustomQuery {
- /// type Err = <i32 as std::str::FromStr>::Err;
- ///
- /// fn from_str(query: &str) -> Result<Self, Self::Err> {
- /// Ok(CustomQuery {
- /// count: query.parse()?,
- /// })
- /// }
- /// }
- ///
- /// // We also need to implement Display for CustomQuery which will be used to format the query string into the URL
- /// impl std::fmt::Display for CustomQuery {
- /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// write!(f, "{}", self.count)
- /// }
- /// }
- ///
- /// # #[component]
- /// # fn Home(query: CustomQuery, other: i32) -> Element {
- /// # unimplemented!()
- /// # }
- /// ```
- #[rustversion::attr(
- since(1.78.0),
- diagnostic::on_unimplemented(
- message = "`FromQueryArgument` is not implemented for `{Self}`",
- label = "query argument",
- note = "FromQueryArgument is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromQueryArgument manually."
- )
- )]
- pub trait FromQueryArgument: Default {
- /// The error that can occur when parsing a query argument.
- type Err;
- /// Create an instance of `Self` from a query string.
- fn from_query_argument(argument: &str) -> Result<Self, Self::Err>;
- }
- impl<T: Default + FromStr> FromQueryArgument for T
- where
- <T as FromStr>::Err: Display,
- {
- type Err = <T as FromStr>::Err;
- fn from_query_argument(argument: &str) -> Result<Self, Self::Err> {
- match T::from_str(argument) {
- Ok(result) => Ok(result),
- Err(err) => {
- tracing::error!("Failed to parse query argument: {}", err);
- Err(err)
- }
- }
- }
- }
- /// 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")]`.
- ///
- ///
- /// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
- ///
- /// # Example
- ///
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// #[derive(Routable, Clone)]
- /// #[rustfmt::skip]
- /// enum Route {
- /// // State is stored in the url hash
- /// #[route("/#:url_hash")]
- /// Home {
- /// url_hash: State,
- /// },
- /// }
- ///
- /// #[component]
- /// fn Home(url_hash: State) -> Element {
- /// unimplemented!()
- /// }
- ///
- ///
- /// #[derive(Clone, PartialEq, Default)]
- /// struct State {
- /// count: usize,
- /// other_count: usize
- /// }
- ///
- /// // The hash segment will be displayed as a string (this will be url encoded automatically)
- /// impl std::fmt::Display for State {
- /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// write!(f, "{}-{}", self.count, self.other_count)
- /// }
- /// }
- ///
- /// // We need to parse the hash fragment into a struct from the string (this will be url decoded automatically)
- /// impl FromHashFragment for State {
- /// fn from_hash_fragment(hash: &str) -> Self {
- /// let Some((first, second)) = hash.split_once('-') else {
- /// // URL fragment parsing shouldn't fail. You can return a default value if you want
- /// return Default::default();
- /// };
- ///
- /// let first = first.parse().unwrap();
- /// let second = second.parse().unwrap();
- ///
- /// State {
- /// count: first,
- /// other_count: second,
- /// }
- /// }
- /// }
- /// ```
- #[rustversion::attr(
- since(1.78.0),
- diagnostic::on_unimplemented(
- message = "`FromHashFragment` is not implemented for `{Self}`",
- label = "hash fragment",
- note = "FromHashFragment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromHashFragment manually."
- )
- )]
- pub trait FromHashFragment {
- /// Create an instance of `Self` from a hash fragment.
- fn from_hash_fragment(hash: &str) -> Self;
- }
- impl<T> FromHashFragment for T
- where
- T: FromStr + Default,
- T::Err: std::fmt::Display,
- {
- fn from_hash_fragment(hash: &str) -> Self {
- match T::from_str(hash) {
- Ok(value) => value,
- Err(err) => {
- tracing::error!("Failed to parse hash fragment: {}", err);
- Default::default()
- }
- }
- }
- }
- /// 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")]`.
- ///
- ///
- /// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
- ///
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// // FromRouteSegment must be implemented for any types you use in the route segment
- /// // When you don't spread the route, you can parse multiple values from the route
- /// // This url will be in the format `/123/456`
- /// #[route("/:route_segment_one/:route_segment_two")]
- /// Home {
- /// route_segment_one: CustomRouteSegment,
- /// route_segment_two: i32,
- /// },
- /// }
- ///
- /// // We can derive Default for CustomRouteSegment
- /// // If the router fails to parse the route segment, it will use the default value instead
- /// #[derive(Default, PartialEq, Clone, Debug)]
- /// struct CustomRouteSegment {
- /// count: i32,
- /// }
- ///
- /// // We implement FromStr for CustomRouteSegment so that FromRouteSegment is implemented automatically
- /// impl std::str::FromStr for CustomRouteSegment {
- /// type Err = <i32 as std::str::FromStr>::Err;
- ///
- /// fn from_str(route_segment: &str) -> Result<Self, Self::Err> {
- /// Ok(CustomRouteSegment {
- /// count: route_segment.parse()?,
- /// })
- /// }
- /// }
- ///
- /// // We also need to implement Display for CustomRouteSegment which will be used to format the route segment into the URL
- /// impl std::fmt::Display for CustomRouteSegment {
- /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// write!(f, "{}", self.count)
- /// }
- /// }
- ///
- /// # #[component]
- /// # fn Home(route_segment_one: CustomRouteSegment, route_segment_two: i32) -> Element {
- /// # unimplemented!()
- /// # }
- /// ```
- #[rustversion::attr(
- since(1.78.0),
- diagnostic::on_unimplemented(
- message = "`FromRouteSegment` is not implemented for `{Self}`",
- label = "route segment",
- note = "FromRouteSegment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromRouteSegment manually."
- )
- )]
- pub trait FromRouteSegment: Sized {
- /// The error that can occur when parsing a route segment.
- type Err;
- /// Create an instance of `Self` from a route segment.
- fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
- }
- impl<T: FromStr> FromRouteSegment for T
- where
- <T as FromStr>::Err: Display,
- {
- type Err = <T as FromStr>::Err;
- fn from_route_segment(route: &str) -> Result<Self, Self::Err> {
- T::from_str(route)
- }
- }
- #[test]
- fn full_circle() {
- let route = "testing 1234 hello world";
- assert_eq!(String::from_route_segment(route).unwrap(), route);
- }
- /// 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")]`.
- ///
- ///
- /// **This trait is automatically implemented for any types that implement `IntoIterator<Item=impl Display>`.**
- ///
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// // FromRouteSegments must be implemented for any types you use in the route segment
- /// // When you spread the route, you can parse multiple values from the route
- /// // This url will be in the format `/123/456/789`
- /// #[route("/:..numeric_route_segments")]
- /// Home {
- /// numeric_route_segments: NumericRouteSegments,
- /// },
- /// }
- ///
- /// // We can derive Default for NumericRouteSegments
- /// // If the router fails to parse the route segment, it will use the default value instead
- /// #[derive(Default, PartialEq, Clone, Debug)]
- /// struct NumericRouteSegments {
- /// numbers: Vec<i32>,
- /// }
- ///
- /// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments
- /// impl ToRouteSegments for NumericRouteSegments {
- /// fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// for number in &self.numbers {
- /// write!(f, "/{}", number)?;
- /// }
- /// Ok(())
- /// }
- /// }
- ///
- /// // We also need to parse the route segments with `FromRouteSegments`
- /// impl FromRouteSegments for NumericRouteSegments {
- /// type Err = <i32 as std::str::FromStr>::Err;
- ///
- /// fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
- /// let mut numbers = Vec::new();
- /// for segment in segments {
- /// numbers.push(segment.parse()?);
- /// }
- /// Ok(NumericRouteSegments { numbers })
- /// }
- /// }
- ///
- /// # #[component]
- /// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {
- /// # unimplemented!()
- /// # }
- /// ```
- pub trait ToRouteSegments {
- /// Display the route segments with each route segment separated by a `/`. This should not start with a `/`.
- ///
- fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
- }
- // Implement ToRouteSegments for any type that can turn &self into an iterator of &T where T: Display
- impl<I, T: Display> ToRouteSegments for I
- where
- for<'a> &'a I: IntoIterator<Item = &'a T>,
- {
- fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- for segment in self {
- write!(f, "/")?;
- let segment = segment.to_string();
- let encoded = urlencoding::encode(&segment);
- write!(f, "{}", encoded)?;
- }
- Ok(())
- }
- }
- #[test]
- fn to_route_segments() {
- struct DisplaysRoute;
- impl Display for DisplaysRoute {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let segments = vec!["hello", "world"];
- segments.display_route_segments(f)
- }
- }
- assert_eq!(DisplaysRoute.to_string(), "/hello/world");
- }
- /// 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")]`.
- ///
- ///
- /// **This trait is automatically implemented for any types that implement `FromIterator<impl Display>`.**
- ///
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// // FromRouteSegments must be implemented for any types you use in the route segment
- /// // When you spread the route, you can parse multiple values from the route
- /// // This url will be in the format `/123/456/789`
- /// #[route("/:..numeric_route_segments")]
- /// Home {
- /// numeric_route_segments: NumericRouteSegments,
- /// },
- /// }
- ///
- /// // We can derive Default for NumericRouteSegments
- /// // If the router fails to parse the route segment, it will use the default value instead
- /// #[derive(Default, Clone, PartialEq, Debug)]
- /// struct NumericRouteSegments {
- /// numbers: Vec<i32>,
- /// }
- ///
- /// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments
- /// impl ToRouteSegments for NumericRouteSegments {
- /// fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- /// for number in &self.numbers {
- /// write!(f, "/{}", number)?;
- /// }
- /// Ok(())
- /// }
- /// }
- ///
- /// // We also need to parse the route segments with `FromRouteSegments`
- /// impl FromRouteSegments for NumericRouteSegments {
- /// type Err = <i32 as std::str::FromStr>::Err;
- ///
- /// fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
- /// let mut numbers = Vec::new();
- /// for segment in segments {
- /// numbers.push(segment.parse()?);
- /// }
- /// Ok(NumericRouteSegments { numbers })
- /// }
- /// }
- ///
- /// # #[component]
- /// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {
- /// # unimplemented!()
- /// # }
- /// ```
- #[rustversion::attr(
- since(1.78.0),
- diagnostic::on_unimplemented(
- message = "`FromRouteSegments` is not implemented for `{Self}`",
- label = "spread route segments",
- 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."
- )
- )]
- pub trait FromRouteSegments: Sized {
- /// The error that can occur when parsing route segments.
- type Err: std::fmt::Display;
- /// Create an instance of `Self` from route segments.
- ///
- /// NOTE: This method must parse the output of `ToRouteSegments::display_route_segments` into the type `Self`.
- fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
- }
- impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
- type Err = <String as FromRouteSegment>::Err;
- fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
- segments
- .iter()
- .map(|s| String::from_route_segment(s))
- .collect()
- }
- }
- /// A flattened version of [`Routable::SITE_MAP`].
- /// This essentially represents a `Vec<Vec<SegmentType>>`, which you can collect it into.
- type SiteMapFlattened<'a> = FlatMap<
- Iter<'a, SiteMapSegment>,
- Vec<Vec<SegmentType>>,
- fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
- >;
- /// The Routable trait is implemented for types that can be converted to and from a route and be rendered as a page.
- ///
- /// A Routable object is something that can be:
- /// 1. Converted from a route.
- /// 2. Converted to a route.
- /// 3. Rendered as a component.
- ///
- /// This trait should generally be derived using the [`dioxus_router_macro::Routable`] macro which has more information about the syntax.
- ///
- /// ## Example
- /// ```rust
- /// use dioxus::prelude::*;
- ///
- /// fn App() -> Element {
- /// rsx! {
- /// Router::<Route> { }
- /// }
- /// }
- ///
- /// // Routes are generally enums that derive `Routable`
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// // Each enum has an associated url
- /// #[route("/")]
- /// Home {},
- /// // Routes can include dynamic segments that are parsed from the url
- /// #[route("/blog/:blog_id")]
- /// Blog { blog_id: usize },
- /// // Or query segments that are parsed from the url
- /// #[route("/edit?:blog_id")]
- /// Edit { blog_id: usize },
- /// // Or hash segments that are parsed from the url
- /// #[route("/hashtag/#:hash")]
- /// Hash { hash: String },
- /// }
- ///
- /// // Each route variant defaults to rendering a component of the same name
- /// #[component]
- /// fn Home() -> Element {
- /// rsx! {
- /// h1 { "Home" }
- /// }
- /// }
- ///
- /// // If the variant has dynamic parameters, those are passed to the component
- /// #[component]
- /// fn Blog(blog_id: usize) -> Element {
- /// rsx! {
- /// h1 { "Blog" }
- /// }
- /// }
- ///
- /// #[component]
- /// fn Edit(blog_id: usize) -> Element {
- /// rsx! {
- /// h1 { "Edit" }
- /// }
- /// }
- ///
- /// #[component]
- /// fn Hash(hash: String) -> Element {
- /// rsx! {
- /// h1 { "Hashtag #{hash}" }
- /// }
- /// }
- /// ```
- #[rustversion::attr(
- since(1.78.0),
- diagnostic::on_unimplemented(
- message = "`Routable` is not implemented for `{Self}`",
- label = "Route",
- note = "Routable should generally be derived using the `#[derive(Routable)]` macro."
- )
- )]
- pub trait Routable: FromStr + Display + Clone + 'static {
- /// The error that can occur when parsing a route.
- const SITE_MAP: &'static [SiteMapSegment];
- /// Render the route at the given level
- fn render(&self, level: usize) -> Element;
- /// Checks if this route is a child of the given route.
- ///
- /// # Example
- /// ```rust
- /// use dioxus_router::prelude::*;
- /// use dioxus::prelude::*;
- ///
- /// #[component]
- /// fn Home() -> Element { VNode::empty() }
- /// #[component]
- /// fn About() -> Element { VNode::empty() }
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// #[route("/")]
- /// Home {},
- /// #[route("/about")]
- /// About {},
- /// }
- ///
- /// let route = Route::About {};
- /// let parent = Route::Home {};
- /// assert!(route.is_child_of(&parent));
- /// ```
- fn is_child_of(&self, other: &Self) -> bool {
- let self_str = self.to_string();
- let self_str = self_str
- .split_once('#')
- .map(|(route, _)| route)
- .unwrap_or(&self_str);
- let self_str = self_str
- .split_once('?')
- .map(|(route, _)| route)
- .unwrap_or(self_str);
- let self_str = self_str.trim_end_matches('/');
- let other_str = other.to_string();
- let other_str = other_str
- .split_once('#')
- .map(|(route, _)| route)
- .unwrap_or(&other_str);
- let other_str = other_str
- .split_once('?')
- .map(|(route, _)| route)
- .unwrap_or(other_str);
- let other_str = other_str.trim_end_matches('/');
- let mut self_segments = self_str.split('/');
- let mut other_segments = other_str.split('/');
- loop {
- match (self_segments.next(), other_segments.next()) {
- // If the two routes are the same length, or this route has less segments, then this segment
- // cannot be the child of the other segment
- (None, Some(_)) | (None, None) => {
- return false;
- }
- // If two segments are not the same, then this segment cannot be the child of the other segment
- (Some(self_seg), Some(other_seg)) => {
- if self_seg != other_seg {
- return false;
- }
- }
- // If the other route has less segments, then this route is the child of the other route
- (Some(_), None) => break,
- }
- }
- true
- }
- /// Get the parent route of this route.
- ///
- /// # Example
- /// ```rust
- /// use dioxus_router::prelude::*;
- /// use dioxus::prelude::*;
- ///
- /// #[component]
- /// fn Home() -> Element { VNode::empty() }
- /// #[component]
- /// fn About() -> Element { VNode::empty() }
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// #[route("/home")]
- /// Home {},
- /// #[route("/home/about")]
- /// About {},
- /// }
- ///
- /// let route = Route::About {};
- /// let parent = route.parent().unwrap();
- /// assert_eq!(parent, Route::Home {});
- /// ```
- fn parent(&self) -> Option<Self> {
- let as_str = self.to_string();
- let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, ""));
- let (route, _) = route_and_query
- .split_once('?')
- .unwrap_or((route_and_query, ""));
- let route = route.trim_end_matches('/');
- let segments = route.split_inclusive('/');
- let segment_count = segments.clone().count();
- let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();
- Self::from_str(&new_route).ok()
- }
- /// Returns a flattened version of [`Self::SITE_MAP`].
- fn flatten_site_map<'a>() -> SiteMapFlattened<'a> {
- Self::SITE_MAP.iter().flat_map(SiteMapSegment::flatten)
- }
- /// Gets a list of all the static routes.
- /// Example static route: `#[route("/static/route")]`
- fn static_routes() -> Vec<Self> {
- Self::flatten_site_map()
- .filter_map(|segments| {
- let mut route = String::new();
- for segment in segments.iter() {
- match segment {
- SegmentType::Static(s) => {
- route.push('/');
- route.push_str(s)
- }
- SegmentType::Child => {}
- _ => return None,
- }
- }
- route.parse().ok()
- })
- .collect()
- }
- }
- /// A type erased map of the site structure.
- #[derive(Debug, Clone, PartialEq)]
- pub struct SiteMapSegment {
- /// The type of the route segment.
- pub segment_type: SegmentType,
- /// The children of the route segment.
- pub children: &'static [SiteMapSegment],
- }
- impl SiteMapSegment {
- /// Take a map of the site structure and flatten it into a vector of routes.
- pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
- let mut routes = Vec::new();
- self.flatten_inner(&mut routes, Vec::new());
- routes
- }
- fn flatten_inner(&self, routes: &mut Vec<Vec<SegmentType>>, current: Vec<SegmentType>) {
- let mut current = current;
- current.push(self.segment_type.clone());
- if self.children.is_empty() {
- routes.push(current);
- } else {
- for child in self.children {
- child.flatten_inner(routes, current.clone());
- }
- }
- }
- }
- /// The type of a route segment.
- #[derive(Debug, Clone, PartialEq)]
- #[non_exhaustive]
- pub enum SegmentType {
- /// A static route segment.
- Static(&'static str),
- /// A dynamic route segment.
- Dynamic(&'static str),
- /// A catch all route segment.
- CatchAll(&'static str),
- /// A child router.
- Child,
- }
- impl SegmentType {
- /// Try to convert this segment into a static segment.
- pub fn to_static(&self) -> Option<&'static str> {
- match self {
- SegmentType::Static(s) => Some(*s),
- _ => None,
- }
- }
- /// Try to convert this segment into a dynamic segment.
- pub fn to_dynamic(&self) -> Option<&'static str> {
- match self {
- SegmentType::Dynamic(s) => Some(*s),
- _ => None,
- }
- }
- /// Try to convert this segment into a catch all segment.
- pub fn to_catch_all(&self) -> Option<&'static str> {
- match self {
- SegmentType::CatchAll(s) => Some(*s),
- _ => None,
- }
- }
- /// Try to convert this segment into a child segment.
- pub fn to_child(&self) -> Option<()> {
- match self {
- SegmentType::Child => Some(()),
- _ => None,
- }
- }
- }
- impl Display for SegmentType {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match &self {
- SegmentType::Static(s) => write!(f, "/{}", s),
- SegmentType::Child => Ok(()),
- SegmentType::Dynamic(s) => write!(f, "/:{}", s),
- SegmentType::CatchAll(s) => write!(f, "/:..{}", s),
- }
- }
- }
|