123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- //! # Routable
- #![allow(non_snake_case)]
- use dioxus::prelude::*;
- use std::{fmt::Display, str::FromStr};
- /// An error that occurs when parsing a route
- #[derive(Debug, PartialEq)]
- pub struct RouteParseError<E: std::fmt::Display> {
- /// The attempted routes that failed to match
- pub attempted_routes: Vec<E>,
- }
- impl<E: std::fmt::Display> std::fmt::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 a query string.
- ///
- /// This trait needs to be implemented if you want to turn a query string into a struct.
- ///
- /// A working example can be found in the `examples` folder in the root package under `query_segments_demo`
- 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(&*urlencoding::decode(query).expect("Failed to decode url encoding"))
- }
- }
- /// Something that can be created from a route segment
- 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: std::fmt::Display,
- {
- type Err = <T as FromStr>::Err;
- fn from_route_segment(route: &str) -> Result<Self, Self::Err> {
- match urlencoding::decode(route) {
- Ok(segment) => T::from_str(&segment),
- Err(err) => {
- log::error!("Failed to decode url encoding: {}", 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 to route segments
- pub trait ToRouteSegments {
- /// Display the route segments
- fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
- }
- impl<I, T: std::fmt::Display> ToRouteSegments for I
- where
- I: IntoIterator<Item = T>,
- {
- fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- for segment in self {
- write!(f, "/")?;
- let segment = segment.to_string();
- match urlencoding::decode(&segment) {
- Ok(segment) => write!(f, "{}", segment)?,
- Err(err) => {
- log::error!("Failed to decode url encoding: {}", err);
- write!(f, "{}", segment)?
- }
- }
- }
- Ok(())
- }
- }
- #[test]
- fn to_route_segments() {
- struct DisplaysRoute;
- impl std::fmt::Display for DisplaysRoute {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let segments = vec!["hello", "world"];
- segments.display_route_segements(f)
- }
- }
- assert_eq!(DisplaysRoute.to_string(), "/hello/world");
- }
- /// Something that can be created from route segments
- pub trait FromRouteSegments: Sized {
- /// The error that can occur when parsing route segments
- type Err;
- /// Create an instance of `Self` from route segments
- 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()
- }
- }
- /// Something that can be:
- /// 1. Converted from a route
- /// 2. Converted to a route
- /// 3. Rendered as a component
- ///
- /// This trait can be derived using the `#[derive(Routable)]` macro
- pub trait Routable: std::fmt::Display + std::str::FromStr + 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<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
- /// Checks if this route is a child of the given route
- ///
- /// # Example
- /// ```rust
- /// use dioxus_router::prelude::*;
- /// use dioxus::prelude::*;
- ///
- /// #[inline_props]
- /// fn Home(cx: Scope) -> Element { todo!() }
- /// #[inline_props]
- /// fn About(cx: Scope) -> Element { todo!() }
- ///
- /// #[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.trim_matches('/');
- let other_str = other.to_string();
- let other_str = other_str.trim_matches('/');
- if other_str.is_empty() {
- return true;
- }
- let self_segments = self_str.split('/');
- let other_segments = other_str.split('/');
- for (self_seg, other_seg) in self_segments.zip(other_segments) {
- if self_seg != other_seg {
- return false;
- }
- }
- true
- }
- /// Get the parent route of this route
- ///
- /// # Example
- /// ```rust
- /// use dioxus_router::prelude::*;
- /// use dioxus::prelude::*;
- ///
- /// #[inline_props]
- /// fn Home(cx: Scope) -> Element { todo!() }
- /// #[inline_props]
- /// fn About(cx: Scope) -> Element { todo!() }
- ///
- /// #[derive(Routable, Clone, PartialEq, Debug)]
- /// enum Route {
- /// #[route("/")]
- /// Home {},
- /// #[route("/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 as_str = as_str.trim_matches('/');
- let segments = as_str.split('/');
- let segment_count = segments.clone().count();
- let new_route = segments
- .take(segment_count - 1)
- .fold(String::new(), |mut acc, segment| {
- acc.push('/');
- acc.push_str(segment);
- acc
- });
- Self::from_str(&new_route).ok()
- }
- /// Gets a list of all static routes
- fn static_routes() -> Vec<Self> {
- Self::SITE_MAP
- .iter()
- .flat_map(|segment| segment.flatten())
- .filter_map(|route| {
- if route
- .iter()
- .all(|segment| matches!(segment, SegmentType::Static(_)))
- {
- Self::from_str(
- &route
- .iter()
- .map(|segment| match segment {
- SegmentType::Static(s) => s.to_string(),
- _ => unreachable!(),
- })
- .collect::<Vec<_>>()
- .join("/"),
- )
- .ok()
- } else {
- None
- }
- })
- .collect()
- }
- }
- trait RoutableFactory {
- type Err: std::fmt::Display;
- type Routable: Routable + FromStr<Err = Self::Err>;
- }
- impl<R: Routable + FromStr> RoutableFactory for R
- where
- <R as FromStr>::Err: std::fmt::Display,
- {
- type Err = <R as FromStr>::Err;
- type Routable = R;
- }
- trait RouteRenderable: std::fmt::Display + 'static {
- fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
- }
- impl<R: Routable> RouteRenderable for R
- where
- <R as FromStr>::Err: std::fmt::Display,
- {
- fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
- self.render(cx, level)
- }
- }
- /// A type erased map of the site structurens
- #[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)]
- 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 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),
- }
- }
- }
|