freya_router/
routable.rs

1//! # Routable
2
3#![allow(non_snake_case)]
4use std::{
5    fmt::Display,
6    iter::FlatMap,
7    slice::Iter,
8    str::FromStr,
9};
10
11use freya_core::integration::Element;
12
13/// An error that occurs when parsing a route.
14#[derive(Debug, PartialEq)]
15pub struct RouteParseError<E: Display> {
16    /// The attempted routes that failed to match.
17    pub attempted_routes: Vec<E>,
18}
19
20impl<E: Display> Display for RouteParseError<E> {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "Route did not match:\nAttempted Matches:\n")?;
23        for (i, route) in self.attempted_routes.iter().enumerate() {
24            writeln!(f, "{}) {route}", i + 1)?;
25        }
26        Ok(())
27    }
28}
29
30#[rustversion::attr(
31    since(1.78.0),
32    diagnostic::on_unimplemented(
33        message = "`FromQuery` is not implemented for `{Self}`",
34        label = "spread query",
35        note = "FromQuery is automatically implemented for types that implement `From<&str>`. You need to either implement From<&str> or implement FromQuery manually."
36    )
37)]
38pub trait FromQuery {
39    /// Create an instance of `Self` from a query string.
40    fn from_query(query: &str) -> Self;
41}
42
43impl<T: for<'a> From<&'a str>> FromQuery for T {
44    fn from_query(query: &str) -> Self {
45        T::from(query)
46    }
47}
48
49#[rustversion::attr(
50    since(1.78.0),
51    diagnostic::on_unimplemented(
52        message = "`FromQueryArgument` is not implemented for `{Self}`",
53        label = "query argument",
54        note = "FromQueryArgument is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromQueryArgument manually."
55    )
56)]
57pub trait FromQueryArgument: Default {
58    /// The error that can occur when parsing a query argument.
59    type Err;
60
61    /// Create an instance of `Self` from a query string.
62    fn from_query_argument(argument: &str) -> Result<Self, Self::Err>;
63}
64
65impl<T: Default + FromStr> FromQueryArgument for T
66where
67    <T as FromStr>::Err: Display,
68{
69    type Err = <T as FromStr>::Err;
70
71    fn from_query_argument(argument: &str) -> Result<Self, Self::Err> {
72        match T::from_str(argument) {
73            Ok(result) => Ok(result),
74            Err(err) => Err(err),
75        }
76    }
77}
78
79#[rustversion::attr(
80    since(1.78.0),
81    diagnostic::on_unimplemented(
82        message = "`FromHashFragment` is not implemented for `{Self}`",
83        label = "hash fragment",
84        note = "FromHashFragment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromHashFragment manually."
85    )
86)]
87pub trait FromHashFragment {
88    /// Create an instance of `Self` from a hash fragment.
89    fn from_hash_fragment(hash: &str) -> Self;
90}
91
92impl<T> FromHashFragment for T
93where
94    T: FromStr + Default,
95    T::Err: std::fmt::Display,
96{
97    fn from_hash_fragment(hash: &str) -> Self {
98        T::from_str(hash).unwrap_or_default()
99    }
100}
101
102#[rustversion::attr(
103    since(1.78.0),
104    diagnostic::on_unimplemented(
105        message = "`FromRouteSegment` is not implemented for `{Self}`",
106        label = "route segment",
107        note = "FromRouteSegment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromRouteSegment manually."
108    )
109)]
110pub trait FromRouteSegment: Sized {
111    /// The error that can occur when parsing a route segment.
112    type Err;
113
114    /// Create an instance of `Self` from a route segment.
115    fn from_route_segment(route: &str) -> Result<Self, Self::Err>;
116}
117
118impl<T: FromStr> FromRouteSegment for T
119where
120    <T as FromStr>::Err: Display,
121{
122    type Err = <T as FromStr>::Err;
123
124    fn from_route_segment(route: &str) -> Result<Self, Self::Err> {
125        T::from_str(route)
126    }
127}
128
129#[test]
130fn full_circle() {
131    let route = "testing 1234 hello world";
132    assert_eq!(String::from_route_segment(route).unwrap(), route);
133}
134
135pub trait ToRouteSegments {
136    /// Display the route segments with each route segment separated by a `/`. This should not start with a `/`.
137    fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
138}
139
140// Implement ToRouteSegments for any type that can turn &self into an iterator of &T where T: Display
141impl<I, T: Display> ToRouteSegments for I
142where
143    for<'a> &'a I: IntoIterator<Item = &'a T>,
144{
145    fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        for segment in self {
147            write!(f, "/")?;
148            let segment = segment.to_string();
149            let encoded = urlencoding::encode(&segment);
150            write!(f, "{encoded}")?;
151        }
152        Ok(())
153    }
154}
155
156#[test]
157fn to_route_segments() {
158    struct DisplaysRoute;
159
160    impl Display for DisplaysRoute {
161        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162            let segments = vec!["hello", "world"];
163            segments.display_route_segments(f)
164        }
165    }
166
167    assert_eq!(DisplaysRoute.to_string(), "/hello/world");
168}
169
170#[rustversion::attr(
171    since(1.78.0),
172    diagnostic::on_unimplemented(
173        message = "`FromRouteSegments` is not implemented for `{Self}`",
174        label = "spread route segments",
175        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."
176    )
177)]
178pub trait FromRouteSegments: Sized {
179    /// The error that can occur when parsing route segments.
180    type Err: std::fmt::Display;
181
182    /// Create an instance of `Self` from route segments.
183    ///
184    /// NOTE: This method must parse the output of `ToRouteSegments::display_route_segments` into the type `Self`.
185    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
186}
187
188impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
189    type Err = <String as FromRouteSegment>::Err;
190
191    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
192        segments
193            .iter()
194            .map(|s| String::from_route_segment(s))
195            .collect()
196    }
197}
198
199/// A flattened version of [`Routable::SITE_MAP`].
200/// This essentially represents a `Vec<Vec<SegmentType>>`, which you can collect it into.
201type SiteMapFlattened<'a> = FlatMap<
202    Iter<'a, SiteMapSegment>,
203    Vec<Vec<SegmentType>>,
204    fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
205>;
206
207#[rustversion::attr(
208    since(1.78.0),
209    diagnostic::on_unimplemented(
210        message = "`Routable` is not implemented for `{Self}`",
211        label = "Route",
212        note = "Routable should generally be derived using the `#[derive(Routable)]` macro."
213    )
214)]
215pub trait Routable: FromStr<Err: Display> + Display + Clone + 'static {
216    /// The error that can occur when parsing a route.
217    const SITE_MAP: &'static [SiteMapSegment];
218
219    /// Render the route at the given level
220    fn render(&self, level: usize) -> Element;
221
222    /// Checks if this route is a child of the given route.
223    fn is_child_of(&self, other: &Self) -> bool {
224        let self_str = self.to_string();
225        let self_str = self_str
226            .split_once('#')
227            .map(|(route, _)| route)
228            .unwrap_or(&self_str);
229        let self_str = self_str
230            .split_once('?')
231            .map(|(route, _)| route)
232            .unwrap_or(self_str);
233        let self_str = self_str.trim_end_matches('/');
234        let other_str = other.to_string();
235        let other_str = other_str
236            .split_once('#')
237            .map(|(route, _)| route)
238            .unwrap_or(&other_str);
239        let other_str = other_str
240            .split_once('?')
241            .map(|(route, _)| route)
242            .unwrap_or(other_str);
243        let other_str = other_str.trim_end_matches('/');
244
245        let mut self_segments = self_str.split('/');
246        let mut other_segments = other_str.split('/');
247        loop {
248            match (self_segments.next(), other_segments.next()) {
249                // If the two routes are the same length, or this route has less segments, then this segment
250                // cannot be the child of the other segment
251                (None, Some(_)) | (None, None) => {
252                    return false;
253                }
254                // If two segments are not the same, then this segment cannot be the child of the other segment
255                (Some(self_seg), Some(other_seg)) => {
256                    if self_seg != other_seg {
257                        return false;
258                    }
259                }
260                // If the other route has less segments, then this route is the child of the other route
261                (Some(_), None) => break,
262            }
263        }
264        true
265    }
266
267    /// Get the parent route of this route.
268    fn parent(&self) -> Option<Self> {
269        let as_str = self.to_string();
270        let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, ""));
271        let (route, _) = route_and_query
272            .split_once('?')
273            .unwrap_or((route_and_query, ""));
274        let route = route.trim_end_matches('/');
275        let segments = route.split_inclusive('/');
276        let segment_count = segments.clone().count();
277        let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();
278        Self::from_str(&new_route).ok()
279    }
280
281    /// Returns a flattened version of [`Self::SITE_MAP`].
282    fn flatten_site_map<'a>() -> SiteMapFlattened<'a> {
283        Self::SITE_MAP.iter().flat_map(SiteMapSegment::flatten)
284    }
285
286    /// Gets a list of all the static routes.
287    /// Example static route: `#[route("/static/route")]`
288    fn static_routes() -> Vec<Self> {
289        Self::flatten_site_map()
290            .filter_map(|segments| {
291                let mut route = String::new();
292                for segment in segments.iter() {
293                    match segment {
294                        SegmentType::Static(s) => {
295                            route.push('/');
296                            route.push_str(s)
297                        }
298                        SegmentType::Child => {}
299                        _ => return None,
300                    }
301                }
302
303                route.parse().ok()
304            })
305            .collect()
306    }
307}
308
309/// A type erased map of the site structure.
310#[derive(Debug, Clone, PartialEq)]
311pub struct SiteMapSegment {
312    /// The type of the route segment.
313    pub segment_type: SegmentType,
314    /// The children of the route segment.
315    pub children: &'static [SiteMapSegment],
316}
317
318impl SiteMapSegment {
319    /// Take a map of the site structure and flatten it into a vector of routes.
320    pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
321        let mut routes = Vec::new();
322        self.flatten_inner(&mut routes, Vec::new());
323        routes
324    }
325
326    fn flatten_inner(&self, routes: &mut Vec<Vec<SegmentType>>, current: Vec<SegmentType>) {
327        let mut current = current;
328        current.push(self.segment_type.clone());
329        if self.children.is_empty() {
330            routes.push(current);
331        } else {
332            for child in self.children {
333                child.flatten_inner(routes, current.clone());
334            }
335        }
336    }
337}
338
339/// The type of a route segment.
340#[derive(Debug, Clone, PartialEq)]
341#[non_exhaustive]
342pub enum SegmentType {
343    /// A static route segment.
344    Static(&'static str),
345    /// A dynamic route segment.
346    Dynamic(&'static str),
347    /// A catch all route segment.
348    CatchAll(&'static str),
349    /// A child router.
350    Child,
351}
352
353impl SegmentType {
354    /// Try to convert this segment into a static segment.
355    pub fn to_static(&self) -> Option<&'static str> {
356        match self {
357            SegmentType::Static(s) => Some(*s),
358            _ => None,
359        }
360    }
361
362    /// Try to convert this segment into a dynamic segment.
363    pub fn to_dynamic(&self) -> Option<&'static str> {
364        match self {
365            SegmentType::Dynamic(s) => Some(*s),
366            _ => None,
367        }
368    }
369
370    /// Try to convert this segment into a catch all segment.
371    pub fn to_catch_all(&self) -> Option<&'static str> {
372        match self {
373            SegmentType::CatchAll(s) => Some(*s),
374            _ => None,
375        }
376    }
377
378    /// Try to convert this segment into a child segment.
379    pub fn to_child(&self) -> Option<()> {
380        match self {
381            SegmentType::Child => Some(()),
382            _ => None,
383        }
384    }
385}
386
387impl Display for SegmentType {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        match &self {
390            SegmentType::Static(s) => write!(f, "/{s}"),
391            SegmentType::Child => Ok(()),
392            SegmentType::Dynamic(s) => write!(f, "/:{s}"),
393            SegmentType::CatchAll(s) => write!(f, "/:..{s}"),
394        }
395    }
396}