freya_router_macro/
query.rs

1use proc_macro2::TokenStream as TokenStream2;
2use quote::quote;
3use syn::{
4    Ident,
5    Type,
6};
7
8#[derive(Debug)]
9pub enum QuerySegment {
10    Single(Box<FullQuerySegment>),
11    Segments(Vec<QueryArgument>),
12}
13
14impl QuerySegment {
15    pub fn contains_ident(&self, ident: &Ident) -> bool {
16        match self {
17            QuerySegment::Single(segment) => segment.ident == *ident,
18            QuerySegment::Segments(segments) => {
19                segments.iter().any(|segment| segment.ident == *ident)
20            }
21        }
22    }
23
24    pub fn parse(&self) -> TokenStream2 {
25        match self {
26            QuerySegment::Single(segment) => segment.parse(),
27            QuerySegment::Segments(segments) => {
28                let mut tokens = TokenStream2::new();
29                tokens.extend(quote! { let split_query: std::collections::HashMap<&str, &str> = query.split('&').filter_map(|s| s.split_once('=')).collect(); });
30                for segment in segments {
31                    tokens.extend(segment.parse());
32                }
33                tokens
34            }
35        }
36    }
37
38    pub fn write(&self) -> TokenStream2 {
39        match self {
40            QuerySegment::Single(segment) => segment.write(),
41            QuerySegment::Segments(segments) => {
42                let mut tokens = TokenStream2::new();
43                tokens.extend(quote! { write!(f, "?")?; });
44                let mut segments_iter = segments.iter();
45                if let Some(first_segment) = segments_iter.next() {
46                    tokens.extend(first_segment.write());
47                }
48                for segment in segments_iter {
49                    tokens.extend(quote! { write!(f, "&")?; });
50                    tokens.extend(segment.write());
51                }
52                tokens
53            }
54        }
55    }
56
57    pub fn parse_from_str<'a>(
58        route_span: proc_macro2::Span,
59        mut fields: impl Iterator<Item = (&'a Ident, &'a Type)>,
60        query: &str,
61    ) -> syn::Result<Self> {
62        // check if the route has a query string
63        if let Some(query) = query.strip_prefix(":..") {
64            let query_ident = Ident::new(query, proc_macro2::Span::call_site());
65            let field = fields.find(|(name, _)| *name == &query_ident);
66
67            let ty = if let Some((_, ty)) = field {
68                ty.clone()
69            } else {
70                return Err(syn::Error::new(
71                    route_span,
72                    format!("Could not find a field with the name '{query_ident}'",),
73                ));
74            };
75
76            Ok(QuerySegment::Single(Box::new(FullQuerySegment {
77                ident: query_ident,
78                ty,
79            })))
80        } else {
81            let mut query_arguments = Vec::new();
82            for segment in query.split('&') {
83                if segment.is_empty() {
84                    return Err(syn::Error::new(
85                        route_span,
86                        "Query segments should be non-empty",
87                    ));
88                }
89                if let Some(query_argument) = segment.strip_prefix(':') {
90                    let query_ident = Ident::new(query_argument, proc_macro2::Span::call_site());
91                    let field = fields.find(|(name, _)| *name == &query_ident);
92
93                    let ty = if let Some((_, ty)) = field {
94                        ty.clone()
95                    } else {
96                        return Err(syn::Error::new(
97                            route_span,
98                            format!("Could not find a field with the name '{query_ident}'",),
99                        ));
100                    };
101
102                    query_arguments.push(QueryArgument {
103                        ident: query_ident,
104                        ty,
105                    });
106                } else {
107                    return Err(syn::Error::new(
108                        route_span,
109                        "Query segments should be a : followed by the name of the query argument",
110                    ));
111                }
112            }
113            Ok(QuerySegment::Segments(query_arguments))
114        }
115    }
116}
117
118#[derive(Debug)]
119pub struct FullQuerySegment {
120    pub ident: Ident,
121    pub ty: Type,
122}
123
124impl FullQuerySegment {
125    pub fn parse(&self) -> TokenStream2 {
126        let ident = &self.ident;
127        let ty = &self.ty;
128        quote! {
129            let #ident = <#ty as freya_router::routable::FromQuery>::from_query(&*query);
130        }
131    }
132
133    pub fn write(&self) -> TokenStream2 {
134        let ident = &self.ident;
135        quote! {
136            {
137                let as_string = #ident.to_string();
138                write!(f, "?{}", freya_router::exports::urlencoding::encode(&as_string))?;
139            }
140        }
141    }
142}
143
144#[derive(Debug)]
145pub struct QueryArgument {
146    pub ident: Ident,
147    pub ty: Type,
148}
149
150impl QueryArgument {
151    pub fn parse(&self) -> TokenStream2 {
152        let ident = &self.ident;
153        let ty = &self.ty;
154        quote! {
155            let #ident = match split_query.get(stringify!(#ident)) {
156                Some(query_argument) => <#ty as freya_router::routable::FromQueryArgument>::from_query_argument(query_argument).unwrap_or_default(),
157                None => <#ty as Default>::default(),
158            };
159        }
160    }
161
162    pub fn write(&self) -> TokenStream2 {
163        let ident = &self.ident;
164        quote! {
165            {
166                let as_string = #ident.to_string();
167                write!(f, "{}={}", stringify!(#ident), freya_router::exports::urlencoding::encode(&as_string))?;
168            }
169        }
170    }
171}