sycamore_router_macro/
parser.rs

1#[derive(Debug, Clone)]
2pub enum SegmentAst {
3    Param(String),
4    DynParam(String),
5    DynSegments(String),
6}
7
8#[derive(Debug)]
9pub struct RoutePathAst {
10    pub(crate) segments: Vec<SegmentAst>,
11}
12
13impl RoutePathAst {
14    pub fn dyn_segments(&self) -> Vec<SegmentAst> {
15        self.segments
16            .iter()
17            .filter(|x| matches!(x, SegmentAst::DynParam(_) | &SegmentAst::DynSegments(_)))
18            .cloned()
19            .collect()
20    }
21}
22
23#[derive(Debug)]
24pub struct ParseError {
25    pub(crate) message: String,
26}
27
28type Result<T, E = ParseError> = std::result::Result<T, E>;
29
30pub fn parse_route(i: &str) -> Result<RoutePathAst> {
31    let i = i.trim_matches('/');
32    let segments = i.split('/');
33    let mut segments_ast = Vec::with_capacity(segments.size_hint().0);
34
35    for segment in segments {
36        if segment.starts_with('<') {
37            if segment.ends_with("..>") {
38                segments_ast.push(SegmentAst::DynSegments(
39                    segment[1..segment.len() - 3].to_string(),
40                ));
41            } else if segment.ends_with('>') {
42                segments_ast.push(SegmentAst::DynParam(
43                    segment[1..segment.len() - 1].to_string(),
44                ));
45            } else {
46                return Err(ParseError {
47                    message: "missing `>` in dynamic segment".to_string(),
48                });
49            }
50        } else if !segment.is_empty() {
51            segments_ast.push(SegmentAst::Param(segment.to_string()));
52        } else if !i.is_empty() {
53            // Do not return this error if we are matching the index page ("/").
54            return Err(ParseError {
55                message: "segment cannot be empty".to_string(),
56            });
57        }
58    }
59
60    Ok(RoutePathAst {
61        segments: segments_ast,
62    })
63}
64
65#[cfg(test)]
66mod tests {
67    use expect_test::{expect, Expect};
68
69    use super::*;
70
71    #[track_caller]
72    fn check(input: &str, expect: Expect) {
73        let actual = format!("{:#?}", parse_route(input).expect("could not parse route"));
74        expect.assert_eq(&actual);
75    }
76
77    #[test]
78    fn index_route() {
79        check(
80            "/",
81            expect![[r#"
82                RoutePathAst {
83                    segments: [],
84                }"#]],
85        );
86    }
87
88    #[test]
89    fn static_route() {
90        check(
91            "/my/static/path",
92            expect![[r#"
93                RoutePathAst {
94                    segments: [
95                        Param(
96                            "my",
97                        ),
98                        Param(
99                            "static",
100                        ),
101                        Param(
102                            "path",
103                        ),
104                    ],
105                }"#]],
106        );
107    }
108
109    #[test]
110    fn route_with_trailing_slash() {
111        check(
112            "/path/",
113            expect![[r#"
114                RoutePathAst {
115                    segments: [
116                        Param(
117                            "path",
118                        ),
119                    ],
120                }"#]],
121        );
122    }
123
124    #[test]
125    fn route_with_no_leading_slash() {
126        check(
127            "my/static/path",
128            expect![[r#"
129                RoutePathAst {
130                    segments: [
131                        Param(
132                            "my",
133                        ),
134                        Param(
135                            "static",
136                        ),
137                        Param(
138                            "path",
139                        ),
140                    ],
141                }"#]],
142        );
143    }
144
145    #[test]
146    fn route_with_no_slash() {
147        check(
148            "path",
149            expect![[r#"
150                RoutePathAst {
151                    segments: [
152                        Param(
153                            "path",
154                        ),
155                    ],
156                }"#]],
157        );
158    }
159
160    #[test]
161    fn dyn_param() {
162        check(
163            "/id/<id>",
164            expect![[r#"
165                RoutePathAst {
166                    segments: [
167                        Param(
168                            "id",
169                        ),
170                        DynParam(
171                            "id",
172                        ),
173                    ],
174                }"#]],
175        );
176    }
177
178    #[test]
179    fn unnamed_dyn_param() {
180        check(
181            "/id/<_>",
182            expect![[r#"
183                RoutePathAst {
184                    segments: [
185                        Param(
186                            "id",
187                        ),
188                        DynParam(
189                            "_",
190                        ),
191                    ],
192                }"#]],
193        );
194    }
195
196    #[test]
197    fn dyn_segments() {
198        check(
199            "/page/<path..>",
200            expect![[r#"
201                RoutePathAst {
202                    segments: [
203                        Param(
204                            "page",
205                        ),
206                        DynSegments(
207                            "path",
208                        ),
209                    ],
210                }"#]],
211        );
212    }
213
214    #[test]
215    fn dyn_param_before_dyn_segment() {
216        check(
217            "/<param>/<segments..>",
218            expect![[r#"
219                RoutePathAst {
220                    segments: [
221                        DynParam(
222                            "param",
223                        ),
224                        DynSegments(
225                            "segments",
226                        ),
227                    ],
228                }"#]],
229        );
230    }
231}