sycamore_router_macro/
route.rs
1use proc_macro2::TokenStream;
2use quote::{quote, quote_spanned, ToTokens};
3use syn::punctuated::Punctuated;
4use syn::spanned::Spanned;
5use syn::{DeriveInput, Fields, Ident, LitStr, Token, Variant};
6
7use crate::parser::{parse_route, RoutePathAst, SegmentAst};
8
9pub fn route_impl(input: DeriveInput) -> syn::Result<TokenStream> {
10 let mut quoted = TokenStream::new();
11 let mut err_quoted = TokenStream::new();
12 let mut error_handler_name = None;
15
16 match &input.data {
17 syn::Data::Enum(de) => {
18 let ty_name = &input.ident;
19
20 for variant in &de.variants {
21 let variant_id = &variant.ident;
22
23 let mut quote_capture_vars = TokenStream::new();
24 let mut route_path_ast = None;
25
26 let mut is_to_route = false;
27
28 for attr in &variant.attrs {
29 let attr_name = match attr.path().get_ident() {
30 Some(ident) => ident.to_string(),
31 None => continue,
32 };
33
34 match attr_name.as_str() {
35 "to" => {
36 let route_litstr: LitStr = attr.parse_args()?;
38 let route_str = route_litstr.value();
39 let route = match parse_route(&route_str) {
40 Ok(route_ast) => route_ast,
41 Err(err) => {
42 return Err(syn::Error::new(route_litstr.span(), err.message));
43 }
44 };
45 quote_capture_vars.extend(impl_to(variant, variant_id, &route)?);
47 route_path_ast = Some(route);
48 is_to_route = true;
49 }
50 "not_found" => {
51 if error_handler_name.is_some() {
52 return Err(syn::Error::new(
53 attr.span(),
54 "cannot have more than one error handler",
55 ));
56 }
57 if !variant.fields.is_empty() {
58 return Err(syn::Error::new(
59 variant.fields.span(),
60 "not found route cannot have any fields",
61 ));
62 }
63 err_quoted = quote! {
64 return Self::#variant_id;
65 };
66 error_handler_name = Some(quote!(Self::#variant_id));
67 }
68 _ => {}
69 }
70 }
71 if is_to_route {
72 let route_path_ast = route_path_ast.unwrap();
73 quoted.extend(quote! {
74 let __route = #route_path_ast;
75 if let Some(__captures) = __route.match_path(__segments) {
76 #quote_capture_vars
78 }
79 });
80 }
81 }
82
83 if error_handler_name.is_none() {
84 return Err(syn::Error::new(
85 input.span(),
86 "not found route not specified",
87 ));
88 }
89
90 Ok(quote! {
91 impl ::sycamore_router::Route for #ty_name {
92 fn match_route(&self, __segments: &[&str]) -> Self {
93 #quoted
94 #err_quoted
95 }
96 }
97 impl ::std::default::Default for #ty_name {
99 fn default() -> Self {
100 #error_handler_name
101 }
102 }
103 })
104 }
105 _ => Err(syn::Error::new(
106 input.span(),
107 "Route can only be derived on enums",
108 )),
109 }
110}
111
112fn impl_to(
114 variant: &Variant,
115 variant_id: &Ident,
116 route: &RoutePathAst,
117) -> Result<TokenStream, syn::Error> {
118 let dyn_segments = route.dyn_segments();
119 let expected_fields_len = dyn_segments.len();
120 if expected_fields_len != variant.fields.len() {
121 return Err(syn::Error::new(
122 variant.fields.span(),
123 format!("mismatch between number of capture fields and variant fields (found {} capture field(s) and {} variant field(s))",
124 expected_fields_len, variant.fields.len()),
125 ));
126 }
127
128 Ok(match &variant.fields {
129 Fields::Named(f) => {
131 let mut captures = Vec::new();
132
133 for (i, (field, segment)) in f.named.iter().zip(dyn_segments.iter()).enumerate() {
134 match segment {
135 SegmentAst::Param(_) => unreachable!("not a dynamic segment"),
136 SegmentAst::DynParam(param) => {
137 if param != &field.ident.as_ref().unwrap().to_string() {
138 return Err(syn::Error::new(
139 field.ident.span(),
140 format!(
141 "capture field name mismatch (expected `{}`, found `{}`)",
142 param,
143 field.ident.as_ref().unwrap()
144 ),
145 ));
146 }
147 let param_id: Ident = syn::parse_str(param)?;
148 captures.push(quote! {
149 let #param_id = match ::sycamore_router::TryFromParam::try_from_param(
150 __captures[#i].as_dyn_param().unwrap()
151 ) {
152 ::std::option::Option::Some(__value) => __value,
153 ::std::option::Option::None => break,
154 };
155 })
156 }
157 SegmentAst::DynSegments(param) => {
158 if param != &field.ident.as_ref().unwrap().to_string() {
159 return Err(syn::Error::new(
160 field.ident.span(),
161 format!(
162 "capture field name mismatch (expected `{}`, found `{}`)",
163 param,
164 field.ident.as_ref().unwrap()
165 ),
166 ));
167 }
168 let param_id: Ident = syn::parse_str(param)?;
169 captures.push(quote! {
170 let #param_id = match ::sycamore_router::TryFromSegments::try_from_segments(
171 __captures[#i].as_dyn_segments().unwrap()
172 ) {
173 ::std::option::Option::Some(__value) => __value,
174 ::std::option::Option::None => break,
175 };
176 })
177 }
178 }
179 }
180 let named: Punctuated<&Option<Ident>, Token![,]> =
181 f.named.iter().map(|x| &x.ident).collect();
182 quote_spanned! {variant.span()=>
183 #[allow(clippy::never_loop)]
184 #[allow(clippy::while_let_loop)]
185 loop {
186 #(#captures)*
187 return Self::#variant_id {
188 #named
189 };
190 }
191 }
192 }
193 Fields::Unnamed(_) => {
195 let mut captures = Vec::new();
196
197 for (i, segment) in dyn_segments.iter().enumerate() {
198 match segment {
199 SegmentAst::Param(_) => unreachable!("not a dynamic segment"),
200 SegmentAst::DynParam(_) => captures.push(quote! {{
201 match ::sycamore_router::TryFromParam::try_from_param(
202 __captures[#i].as_dyn_param().unwrap()
203 ) {
204 ::std::option::Option::Some(__value) => __value,
205 ::std::option::Option::None => break,
206 }
207 }}),
208 SegmentAst::DynSegments(_) => captures.push(quote! {{
209 match ::sycamore_router::TryFromSegments::try_from_segments(
210 __captures[#i].as_dyn_segments().unwrap()
211 ) {
212 ::std::option::Option::Some(__value) => __value,
213 ::std::option::Option::None => break,
214 }
215 }}),
216 }
217 }
218 quote! {
219 loop {
221 return Self::#variant_id(#(#captures),*);
222 }
223 }
224 }
225 Fields::Unit => quote! {
226 return Self::#variant_id;
227 },
228 })
229}
230
231impl ToTokens for SegmentAst {
232 fn to_tokens(&self, tokens: &mut TokenStream) {
233 match self {
234 SegmentAst::Param(param) => tokens.extend(quote! {
235 ::sycamore_router::Segment::Param(::std::string::ToString::to_string(#param))
236 }),
237 SegmentAst::DynParam(_) => tokens.extend(quote! {
238 ::sycamore_router::Segment::DynParam
239 }),
240 SegmentAst::DynSegments(_) => tokens.extend(quote! {
241 ::sycamore_router::Segment::DynSegments
242 }),
243 }
244 }
245}
246
247impl ToTokens for RoutePathAst {
248 fn to_tokens(&self, tokens: &mut TokenStream) {
249 let segments = self
250 .segments
251 .iter()
252 .map(|s| s.to_token_stream())
253 .collect::<Vec<_>>();
254
255 tokens.extend(quote! {
256 ::sycamore_router::RoutePath::new(::std::vec![#(#segments),*])
257 });
258 }
259}