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 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}