freya_router_macro/
query.rs1use 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 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}