Skip to main content

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
135/// Formats a value as one or more URL path segments.
136///
137/// This is used by `#[derive(Routable)]` for catch-all route fields.
138pub trait ToRouteSegments {
139    /// Display the route segments with each route segment separated by a `/`. This should not start with a `/`.
140    fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
141}
142
143// Implement ToRouteSegments for any type that can turn &self into an iterator of &T where T: Display
144impl<I, T: Display> ToRouteSegments for I
145where
146    for<'a> &'a I: IntoIterator<Item = &'a T>,
147{
148    fn display_route_segments(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        for segment in self {
150            write!(f, "/")?;
151            let segment = segment.to_string();
152            let encoded = urlencoding::encode(&segment);
153            write!(f, "{encoded}")?;
154        }
155        Ok(())
156    }
157}
158
159#[test]
160fn to_route_segments() {
161    struct DisplaysRoute;
162
163    impl Display for DisplaysRoute {
164        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165            let segments = vec!["hello", "world"];
166            segments.display_route_segments(f)
167        }
168    }
169
170    assert_eq!(DisplaysRoute.to_string(), "/hello/world");
171}
172
173#[rustversion::attr(
174    since(1.78.0),
175    diagnostic::on_unimplemented(
176        message = "`FromRouteSegments` is not implemented for `{Self}`",
177        label = "spread route segments",
178        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."
179    )
180)]
181pub trait FromRouteSegments: Sized {
182    /// The error that can occur when parsing route segments.
183    type Err: std::fmt::Display;
184
185    /// Create an instance of `Self` from route segments.
186    ///
187    /// NOTE: This method must parse the output of `ToRouteSegments::display_route_segments` into the type `Self`.
188    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err>;
189}
190
191impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
192    type Err = <String as FromRouteSegment>::Err;
193
194    fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
195        segments
196            .iter()
197            .map(|s| String::from_route_segment(s))
198            .collect()
199    }
200}
201
202/// A flattened version of [`Routable::SITE_MAP`].
203/// This essentially represents a `Vec<Vec<SegmentType>>`, which you can collect it into.
204type SiteMapFlattened<'a> = FlatMap<
205    Iter<'a, SiteMapSegment>,
206    Vec<Vec<SegmentType>>,
207    fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
208>;
209
210#[rustversion::attr(
211    since(1.78.0),
212    diagnostic::on_unimplemented(
213        message = "`Routable` is not implemented for `{Self}`",
214        label = "Route",
215        note = "Routable should generally be derived using the `#[derive(Routable)]` macro."
216    )
217)]
218pub trait Routable: FromStr<Err: Display> + Display + Clone + 'static {
219    /// The error that can occur when parsing a route.
220    const SITE_MAP: &'static [SiteMapSegment];
221
222    /// Renders the route at `level` in the nested route hierarchy.
223    ///
224    /// Implementations are normally generated by `#[derive(Routable)]`.
225    fn render(&self, level: usize) -> Element;
226
227    /// Returns whether this route is nested below `other`.
228    ///
229    /// Query parameters and hash fragments do not affect the result.
230    fn is_child_of(&self, other: &Self) -> bool {
231        let self_str = self.to_string();
232        let self_str = self_str
233            .split_once('#')
234            .map(|(route, _)| route)
235            .unwrap_or(&self_str);
236        let self_str = self_str
237            .split_once('?')
238            .map(|(route, _)| route)
239            .unwrap_or(self_str);
240        let self_str = self_str.trim_end_matches('/');
241        let other_str = other.to_string();
242        let other_str = other_str
243            .split_once('#')
244            .map(|(route, _)| route)
245            .unwrap_or(&other_str);
246        let other_str = other_str
247            .split_once('?')
248            .map(|(route, _)| route)
249            .unwrap_or(other_str);
250        let other_str = other_str.trim_end_matches('/');
251
252        let mut self_segments = self_str.split('/');
253        let mut other_segments = other_str.split('/');
254        loop {
255            match (self_segments.next(), other_segments.next()) {
256                // If the two routes are the same length, or this route has less segments, then this segment
257                // cannot be the child of the other segment
258                (None, Some(_)) | (None, None) => {
259                    return false;
260                }
261                // If two segments are not the same, then this segment cannot be the child of the other segment
262                (Some(self_seg), Some(other_seg)) => {
263                    if self_seg != other_seg {
264                        return false;
265                    }
266                }
267                // If the other route has less segments, then this route is the child of the other route
268                (Some(_), None) => break,
269            }
270        }
271        true
272    }
273
274    /// Returns this route's parent, if it can be parsed as the same route type.
275    fn parent(&self) -> Option<Self> {
276        let as_str = self.to_string();
277        let (route_and_query, _) = as_str.split_once('#').unwrap_or((&as_str, ""));
278        let (route, _) = route_and_query
279            .split_once('?')
280            .unwrap_or((route_and_query, ""));
281        let route = route.trim_end_matches('/');
282        let segments = route.split_inclusive('/');
283        let segment_count = segments.clone().count();
284        let new_route: String = segments.take(segment_count.saturating_sub(1)).collect();
285        Self::from_str(&new_route).ok()
286    }
287
288    /// Returns every route pattern in the generated site map as path segments.
289    fn flatten_site_map<'a>() -> SiteMapFlattened<'a> {
290        Self::SITE_MAP.iter().flat_map(SiteMapSegment::flatten)
291    }
292
293    /// Returns all routes that have no dynamic or catch-all segments.
294    ///
295    /// For example, `#[route("/static/route")]` is static.
296    fn static_routes() -> Vec<Self> {
297        Self::flatten_site_map()
298            .filter_map(|segments| {
299                let mut route = String::new();
300                for segment in segments.iter() {
301                    match segment {
302                        SegmentType::Static(s) => {
303                            route.push('/');
304                            route.push_str(s)
305                        }
306                        SegmentType::Child => {}
307                        _ => return None,
308                    }
309                }
310
311                route.parse().ok()
312            })
313            .collect()
314    }
315}
316
317/// A type erased map of the site structure.
318#[derive(Debug, Clone, PartialEq)]
319pub struct SiteMapSegment {
320    /// The type of the route segment.
321    pub segment_type: SegmentType,
322    /// The children of the route segment.
323    pub children: &'static [SiteMapSegment],
324}
325
326impl SiteMapSegment {
327    /// Flattens this branch of the site map into complete route patterns.
328    pub fn flatten(&self) -> Vec<Vec<SegmentType>> {
329        let mut routes = Vec::new();
330        self.flatten_inner(&mut routes, Vec::new());
331        routes
332    }
333
334    fn flatten_inner(&self, routes: &mut Vec<Vec<SegmentType>>, current: Vec<SegmentType>) {
335        let mut current = current;
336        current.push(self.segment_type.clone());
337        if self.children.is_empty() {
338            routes.push(current);
339        } else {
340            for child in self.children {
341                child.flatten_inner(routes, current.clone());
342            }
343        }
344    }
345}
346
347/// The type of a route segment.
348#[derive(Debug, Clone, PartialEq)]
349#[non_exhaustive]
350pub enum SegmentType {
351    /// A static route segment.
352    Static(&'static str),
353    /// A dynamic route segment.
354    Dynamic(&'static str),
355    /// A catch all route segment.
356    CatchAll(&'static str),
357    /// A child router.
358    Child,
359}
360
361impl SegmentType {
362    /// Try to convert this segment into a static segment.
363    pub fn to_static(&self) -> Option<&'static str> {
364        match self {
365            SegmentType::Static(s) => Some(*s),
366            _ => None,
367        }
368    }
369
370    /// Try to convert this segment into a dynamic segment.
371    pub fn to_dynamic(&self) -> Option<&'static str> {
372        match self {
373            SegmentType::Dynamic(s) => Some(*s),
374            _ => None,
375        }
376    }
377
378    /// Try to convert this segment into a catch all segment.
379    pub fn to_catch_all(&self) -> Option<&'static str> {
380        match self {
381            SegmentType::CatchAll(s) => Some(*s),
382            _ => None,
383        }
384    }
385
386    /// Try to convert this segment into a child segment.
387    pub fn to_child(&self) -> Option<()> {
388        match self {
389            SegmentType::Child => Some(()),
390            _ => None,
391        }
392    }
393}
394
395impl Display for SegmentType {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        match &self {
398            SegmentType::Static(s) => write!(f, "/{s}"),
399            SegmentType::Child => Ok(()),
400            SegmentType::Dynamic(s) => write!(f, "/:{s}"),
401            SegmentType::CatchAll(s) => write!(f, "/:..{s}"),
402        }
403    }
404}