Routable improvements (#1461)
* Fix `SegmentType::CatchAll` `Display` * Rename `display_route_segements` to `display_route_segments` and add dots to docs * Fix `Routeable::static_routes` and add more route retrieval methods * Fix Clippy and extract duplication to function * Return route strings instead of instances * Remove some methods * Clippy
This commit is contained in:
1 parent
300cbf4fc6
commit
ae5dca8f43
3 files changed
+91
-69
No files matched your search
@@ -210,7 +210,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
|
|||||||
let display_impl = route_enum.impl_display();
|
let display_impl = route_enum.impl_display();
|
||||||
let routable_impl = route_enum.routable_impl();
|
let routable_impl = route_enum.routable_impl();
|
||||||
|
|
||||||
quote! {
|
(quote! {
|
||||||
#error_type
|
#error_type
|
||||||
|
|
||||||
#display_impl
|
#display_impl
|
||||||
@@ -218,7 +218,7 @@ pub fn routable(input: TokenStream) -> TokenStream {
|
|||||||
#routable_impl
|
#routable_impl
|
||||||
|
|
||||||
#parse_impl
|
#parse_impl
|
||||||
}
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,9 +678,9 @@ impl ToTokens for SegmentType {
|
|||||||
impl<'a> From<&'a RouteSegment> for SegmentType {
|
impl<'a> From<&'a RouteSegment> for SegmentType {
|
||||||
fn from(value: &'a RouteSegment) -> Self {
|
fn from(value: &'a RouteSegment) -> Self {
|
||||||
match value {
|
match value {
|
||||||
segment::RouteSegment::Static(s) => SegmentType::Static(s.to_string()),
|
RouteSegment::Static(s) => SegmentType::Static(s.to_string()),
|
||||||
segment::RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()),
|
RouteSegment::Dynamic(s, _) => SegmentType::Dynamic(s.to_string()),
|
||||||
segment::RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()),
|
RouteSegment::CatchAll(s, _) => SegmentType::CatchAll(s.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ impl RouteSegment {
|
|||||||
match self {
|
match self {
|
||||||
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
|
Self::Static(segment) => quote! { write!(f, "/{}", #segment)?; },
|
||||||
Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; },
|
Self::Dynamic(ident, _) => quote! { write!(f, "/{}", #ident)?; },
|
||||||
Self::CatchAll(ident, _) => quote! { #ident.display_route_segements(f)?; },
|
Self::CatchAll(ident, _) => quote! { #ident.display_route_segments(f)?; },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,18 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
use std::iter::FlatMap;
|
||||||
|
use std::slice::Iter;
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
/// An error that occurs when parsing a route
|
/// An error that occurs when parsing a route.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct RouteParseError<E: std::fmt::Display> {
|
pub struct RouteParseError<E: Display> {
|
||||||
/// The attempted routes that failed to match
|
/// The attempted routes that failed to match.
|
||||||
pub attempted_routes: Vec<E>,
|
pub attempted_routes: Vec<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
|
impl<E: Display> Display for RouteParseError<E> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "Route did not match:\nAttempted Matches:\n")?;
|
write!(f, "Route did not match:\nAttempted Matches:\n")?;
|
||||||
for (i, route) in self.attempted_routes.iter().enumerate() {
|
for (i, route) in self.attempted_routes.iter().enumerate() {
|
||||||
@@ -26,9 +28,9 @@ impl<E: std::fmt::Display> std::fmt::Display for RouteParseError<E> {
|
|||||||
///
|
///
|
||||||
/// This trait needs to be implemented if you want to turn a query string into a struct.
|
/// 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`
|
/// A working example can be found in the `examples` folder in the root package under `query_segments_demo`.
|
||||||
pub trait FromQuery {
|
pub trait FromQuery {
|
||||||
/// Create an instance of `Self` from a query string
|
/// Create an instance of `Self` from a query string.
|
||||||
fn from_query(query: &str) -> Self;
|
fn from_query(query: &str) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,18 +40,18 @@ impl<T: for<'a> From<&'a str>> FromQuery for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Something that can be created from a route segment
|
/// Something that can be created from a route segment.
|
||||||
pub trait FromRouteSegment: Sized {
|
pub trait FromRouteSegment: Sized {
|
||||||
/// The error that can occur when parsing a route segment
|
/// The error that can occur when parsing a route segment.
|
||||||
type Err;
|
type Err;
|
||||||
|
|
||||||
/// Create an instance of `Self` from a route segment
|
/// Create an instance of `Self` from a route segment.
|
||||||
fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
|
fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: FromStr> FromRouteSegment for T
|
impl<T: FromStr> FromRouteSegment for T
|
||||||
where
|
where
|
||||||
<T as FromStr>::Err: std::fmt::Display,
|
<T as FromStr>::Err: Display,
|
||||||
{
|
{
|
||||||
type Err = <T as FromStr>::Err;
|
type Err = <T as FromStr>::Err;
|
||||||
|
|
||||||
@@ -70,17 +72,17 @@ fn full_circle() {
|
|||||||
assert_eq!(String::from_route_segment(route).unwrap(), route);
|
assert_eq!(String::from_route_segment(route).unwrap(), route);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Something that can be converted to route segments
|
/// Something that can be converted to route segments.
|
||||||
pub trait ToRouteSegments {
|
pub trait ToRouteSegments {
|
||||||
/// Display the route segments
|
/// Display the route segments.
|
||||||
fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, T: std::fmt::Display> ToRouteSegments for I
|
impl<I, T: Display> ToRouteSegments for I
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = T>,
|
I: IntoIterator<Item = T>,
|
||||||
{
|
{
|
||||||
fn display_route_segements(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
for segment in self {
|
for segment in self {
|
||||||
write!(f, "/")?;
|
write!(f, "/")?;
|
||||||
let segment = segment.to_string();
|
let segment = segment.to_string();
|
||||||
@@ -100,22 +102,22 @@ where
|
|||||||
fn to_route_segments() {
|
fn to_route_segments() {
|
||||||
struct DisplaysRoute;
|
struct DisplaysRoute;
|
||||||
|
|
||||||
impl std::fmt::Display for DisplaysRoute {
|
impl Display for DisplaysRoute {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let segments = vec!["hello", "world"];
|
let segments = vec!["hello", "world"];
|
||||||
segments.display_route_segements(f)
|
segments.display_route_segments(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(DisplaysRoute.to_string(), "/hello/world");
|
assert_eq!(DisplaysRoute.to_string(), "/hello/world");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Something that can be created from route segments
|
/// Something that can be created from route segments.
|
||||||
pub trait FromRouteSegments: Sized {
|
pub trait FromRouteSegments: Sized {
|
||||||
/// The error that can occur when parsing route segments
|
/// The error that can occur when parsing route segments.
|
||||||
type Err;
|
type Err;
|
||||||
|
|
||||||
/// Create an instance of `Self` from route segments
|
/// Create an instance of `Self` from route segments.
|
||||||
fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
|
fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,20 +132,45 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn seg_strs_to_route<T>(segs_maybe: &Option<Vec<&str>>) -> Option<T>
|
||||||
|
where
|
||||||
|
T: Routable,
|
||||||
|
{
|
||||||
|
if let Some(str) = seg_strs_to_str(segs_maybe) {
|
||||||
|
T::from_str(&str).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seg_strs_to_str(segs_maybe: &Option<Vec<&str>>) -> Option<String> {
|
||||||
|
segs_maybe
|
||||||
|
.as_ref()
|
||||||
|
.map(|segs| String::from('/') + &segs.join("/"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Something that can be:
|
/// Something that can be:
|
||||||
/// 1. Converted from a route
|
/// 1. Converted from a route.
|
||||||
/// 2. Converted to a route
|
/// 2. Converted to a route.
|
||||||
/// 3. Rendered as a component
|
/// 3. Rendered as a component.
|
||||||
///
|
///
|
||||||
/// This trait can be derived using the `#[derive(Routable)]` macro
|
/// This trait can be derived using the `#[derive(Routable)]` macro.
|
||||||
pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
|
pub trait Routable: FromStr + Display + Clone + 'static {
|
||||||
/// The error that can occur when parsing a route
|
/// The error that can occur when parsing a route.
|
||||||
const SITE_MAP: &'static [SiteMapSegment];
|
const SITE_MAP: &'static [SiteMapSegment];
|
||||||
|
|
||||||
/// Render the route at the given level
|
/// Render the route at the given level
|
||||||
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
|
||||||
|
|
||||||
/// Checks if this route is a child of the given route
|
/// Checks if this route is a child of the given route.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@@ -185,7 +212,7 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the parent route of this route
|
/// Get the parent route of this route.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@@ -225,72 +252,67 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
|
|||||||
Self::from_str(&new_route).ok()
|
Self::from_str(&new_route).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a list of all static routes
|
/// 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> {
|
fn static_routes() -> Vec<Self> {
|
||||||
Self::SITE_MAP
|
Self::flatten_site_map()
|
||||||
.iter()
|
|
||||||
.flat_map(|segment| segment.flatten())
|
|
||||||
.filter_map(|route| {
|
.filter_map(|route| {
|
||||||
if route
|
let route_if_static = &route
|
||||||
.iter()
|
.iter()
|
||||||
.all(|segment| matches!(segment, SegmentType::Static(_)))
|
.map(|segment| match segment {
|
||||||
{
|
SegmentType::Static(s) => Some(*s),
|
||||||
Self::from_str(
|
_ => None,
|
||||||
&route
|
})
|
||||||
.iter()
|
.collect::<Option<Vec<_>>>();
|
||||||
.map(|segment| match segment {
|
|
||||||
SegmentType::Static(s) => s.to_string(),
|
seg_strs_to_route(route_if_static)
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("/"),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait RoutableFactory {
|
trait RoutableFactory {
|
||||||
type Err: std::fmt::Display;
|
type Err: Display;
|
||||||
type Routable: Routable + FromStr<Err = Self::Err>;
|
type Routable: Routable + FromStr<Err = Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable + FromStr> RoutableFactory for R
|
impl<R: Routable + FromStr> RoutableFactory for R
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: Display,
|
||||||
{
|
{
|
||||||
type Err = <R as FromStr>::Err;
|
type Err = <R as FromStr>::Err;
|
||||||
type Routable = R;
|
type Routable = R;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait RouteRenderable: std::fmt::Display + 'static {
|
trait RouteRenderable: Display + 'static {
|
||||||
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable> RouteRenderable for R
|
impl<R: Routable> RouteRenderable for R
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: Display,
|
||||||
{
|
{
|
||||||
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
|
fn render<'a>(&self, cx: &'a ScopeState, level: usize) -> Element<'a> {
|
||||||
self.render(cx, level)
|
self.render(cx, level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type erased map of the site structurens
|
/// A type erased map of the site structure.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct SiteMapSegment {
|
pub struct SiteMapSegment {
|
||||||
/// The type of the route segment
|
/// The type of the route segment.
|
||||||
pub segment_type: SegmentType,
|
pub segment_type: SegmentType,
|
||||||
/// The children of the route segment
|
/// The children of the route segment.
|
||||||
pub children: &'static [SiteMapSegment],
|
pub children: &'static [SiteMapSegment],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteMapSegment {
|
impl SiteMapSegment {
|
||||||
/// Take a map of the site structure and flatten it into a vector of routes
|
/// Take a map of the site structure and flatten it into a vector of routes.
|
||||||
pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
|
pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
|
||||||
let mut routes = Vec::new();
|
let mut routes = Vec::new();
|
||||||
self.flatten_inner(&mut routes, Vec::new());
|
self.flatten_inner(&mut routes, Vec::new());
|
||||||
@@ -310,16 +332,16 @@ impl SiteMapSegment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of a route segment
|
/// The type of a route segment.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum SegmentType {
|
pub enum SegmentType {
|
||||||
/// A static route segment
|
/// A static route segment.
|
||||||
Static(&'static str),
|
Static(&'static str),
|
||||||
/// A dynamic route segment
|
/// A dynamic route segment.
|
||||||
Dynamic(&'static str),
|
Dynamic(&'static str),
|
||||||
/// A catch all route segment
|
/// A catch all route segment.
|
||||||
CatchAll(&'static str),
|
CatchAll(&'static str),
|
||||||
/// A child router
|
/// A child router.
|
||||||
Child,
|
Child,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +351,7 @@ impl Display for SegmentType {
|
|||||||
SegmentType::Static(s) => write!(f, "/{}", s),
|
SegmentType::Static(s) => write!(f, "/{}", s),
|
||||||
SegmentType::Child => Ok(()),
|
SegmentType::Child => Ok(()),
|
||||||
SegmentType::Dynamic(s) => write!(f, "/:{}", s),
|
SegmentType::Dynamic(s) => write!(f, "/:{}", s),
|
||||||
SegmentType::CatchAll(s) => write!(f, "/:...{}", s),
|
SegmentType::CatchAll(s) => write!(f, "/:..{}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user