1use std::fmt::Display;
2
3use devise::{Result, ext::{SpanDiagnosticExt, quote_respanned}};
4use syn::{Expr, Ident, Type, spanned::Spanned};
5use proc_macro2::TokenStream;
6
7use crate::http::uri::fmt;
8use crate::http_codegen::Optional;
9use crate::syn_ext::IdentExt;
10use crate::bang::uri_parsing::*;
11use crate::attribute::param::Parameter;
12use crate::exports::*;
13use crate::URI_MACRO_PREFIX;
14
15macro_rules! p {
16 (@go $num:expr, $singular:expr, $plural:expr) => (
17 if $num == 1 { $singular.into() } else { $plural }
18 );
19
20 ("parameter", $n:expr) => (p!(@go $n, "parameter", "parameters"));
21 ($n:expr, "was") => (p!(@go $n, "1 was", format!("{} were", $n)));
22 ($n:expr, "parameter") => (p!(@go $n, "1 parameter", format!("{} parameters", $n)));
23}
24
25pub fn prefix_last_segment(path: &mut syn::Path, prefix: &str) {
26 let last_seg = path.segments.last_mut().expect("syn::Path has segments");
27 last_seg.ident = last_seg.ident.prepend(prefix);
28}
29
30pub fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
31 let input2: TokenStream = input.clone();
32 match syn::parse2::<UriMacro>(input)? {
33 UriMacro::Routed(ref mut mac) => {
34 prefix_last_segment(&mut mac.route.path, URI_MACRO_PREFIX);
35 let path = &mac.route.path;
36 Ok(quote!(#path!(#input2)))
37 },
38 UriMacro::Literal(uri) => Ok(quote!(#uri)),
39 }
40}
41
42fn extract_exprs(internal: &InternalUriParams) -> Result<(
43 impl Iterator<Item = &Expr>, impl Iterator<Item = &ArgExpr>, impl Iterator<Item = (&Ident, &Type)>, )>
47{
48 let route_name = &internal.uri_mac.route.path;
49 match internal.validate() {
50 Validation::Ok(exprs) => {
51 let path_params = internal.dynamic_path_params();
52 let path_param_count = path_params.clone().count();
53 for expr in exprs.iter().take(path_param_count) {
54 if expr.as_expr().is_none() {
55 return Err(expr.span().error("path parameters cannot be ignored"));
56 }
57 }
58
59 let query_exprs = exprs.clone().into_iter().skip(path_param_count);
60 let path_exprs = exprs.into_iter().map(|e| e.unwrap_expr()).take(path_param_count);
61 let types = internal.fn_args.iter().map(|a| (&a.ident, &a.ty));
62 Ok((path_exprs, query_exprs, types))
63 }
64 Validation::NamedIgnored(_) => {
65 let mut route_name = quote!(#route_name).to_string();
66 route_name.retain(|c| !c.is_whitespace());
67
68 let diag = internal.uri_mac.args_span()
69 .error("expected unnamed arguments due to ignored parameters")
70 .note(format!("uri for route `{}` ignores path parameters: \"{}\"",
71 route_name, internal.route_uri));
72
73 Err(diag)
74 }
75 Validation::Unnamed(expected, actual) => {
76 let mut route_name = quote!(#route_name).to_string();
77 route_name.retain(|c| !c.is_whitespace());
78
79 let diag = internal.uri_mac.args_span()
80 .error(format!("route expects {} but {} supplied",
81 p!(expected, "parameter"), p!(actual, "was")))
82 .note(format!("route `{}` has uri \"{}\"", route_name, internal.route_uri));
83
84 Err(diag)
85 }
86 Validation::Named(missing, extra, dup) => {
87 let e = format!("invalid parameters for `{}` route uri", quote!(#route_name));
88 let mut diag = internal.uri_mac.args_span().error(e)
89 .note(format!("uri parameters are: {}", internal.fn_args_str()));
90
91 fn join<S: Display, T: Iterator<Item = S>>(iter: T) -> (&'static str, String) {
92 let mut items: Vec<_> = iter.map(|i| format!("`{}`", i)).collect();
93 items.dedup();
94 (p!("parameter", items.len()), items.join(", "))
95 }
96
97 if !missing.is_empty() {
98 let (ps, msg) = join(missing.iter());
99 diag = diag.help(format!("missing {}: {}", ps, msg));
100 }
101
102 if !extra.is_empty() {
103 let (ps, msg) = join(extra.iter());
104 let spans: Vec<_> = extra.iter().map(|ident| ident.span()).collect();
105 diag = diag.span_help(spans, format!("unknown {}: {}", ps, msg));
106 }
107
108 if !dup.is_empty() {
109 let (ps, msg) = join(dup.iter());
110 let spans: Vec<_> = dup.iter().map(|ident| ident.span()).collect();
111 diag = diag.span_help(spans, format!("duplicate {}: {}", ps, msg));
112 }
113
114 Err(diag)
115 }
116 }
117}
118
119fn add_binding<P: fmt::Part>(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr) {
120 let span = expr.span();
121 let part = match P::KIND {
122 fmt::Kind::Path => quote_spanned!(span => #_fmt::Path),
123 fmt::Kind::Query => quote_spanned!(span => #_fmt::Query),
124 };
125
126 let tmp_ident = ident.clone().with_span(expr.span());
127 let let_stmt = quote_spanned!(span => let #tmp_ident = #expr);
128
129 to.push(quote_spanned!(mixed(span) =>
130 #[allow(non_snake_case)] #let_stmt;
131 let #ident = <#ty as #_fmt::FromUriParam<#part, _>>::from_uri_param(#tmp_ident);
132 ));
133}
134
135fn explode_path<'a>(
136 internal: &InternalUriParams,
137 bindings: &mut Vec<TokenStream>,
138 mut exprs: impl Iterator<Item = &'a Expr>,
139 mut args: impl Iterator<Item = (&'a Ident, &'a Type)>,
140) -> TokenStream {
141 if internal.dynamic_path_params().count() == 0 {
142 let path = internal.route_uri.path().as_str();
143 quote!(#_fmt::UriArgumentsKind::Static(#path))
144 } else {
145 let uri_display = quote!(#_fmt::UriDisplay<#_fmt::Path>);
146 let dyn_exprs = internal.path_params.iter().map(|param| {
147 match param {
148 Parameter::Static(name) => {
149 quote!(&#name as &dyn #uri_display)
150 },
151 Parameter::Dynamic(_) | Parameter::Guard(_) => {
152 let (ident, ty) = args.next().expect("ident/ty for non-ignored");
153 let expr = exprs.next().expect("one expr per dynamic arg");
154 add_binding::<fmt::Path>(bindings, &ident, &ty, &expr);
155 quote_spanned!(expr.span() => &#ident as &dyn #uri_display)
156 }
157 Parameter::Ignored(_) => {
158 let expr = exprs.next().expect("one expr per dynamic arg");
159 quote_spanned!(expr.span() => &#expr as &dyn #uri_display)
160 }
161 }
162 });
163
164 quote!(#_fmt::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
165 }
166}
167
168fn explode_query<'a>(
169 internal: &InternalUriParams,
170 bindings: &mut Vec<TokenStream>,
171 mut arg_exprs: impl Iterator<Item = &'a ArgExpr>,
172 mut args: impl Iterator<Item = (&'a Ident, &'a Type)>,
173) -> Option<TokenStream> {
174 let query = internal.route_uri.query()?.as_str();
175 if internal.dynamic_query_params().count() == 0 {
176 return Some(quote!(#_fmt::UriArgumentsKind::Static(#query)));
177 }
178
179 let query_arg = quote!(#_fmt::UriQueryArgument);
180 let uri_display = quote!(#_fmt::UriDisplay<#_fmt::Query>);
181 let dyn_exprs = internal.query_params.iter().filter_map(|param| {
182 if let Parameter::Static(source) = param {
183 return Some(quote!(#query_arg::Raw(#source)));
184 }
185
186 let dynamic = match param {
187 Parameter::Static(source) => {
188 return Some(quote!(#query_arg::Raw(#source)));
189 },
190 Parameter::Dynamic(ref seg) => seg,
191 Parameter::Guard(ref seg) => seg,
192 Parameter::Ignored(_) => unreachable!("invariant: unignorable q")
193 };
194
195 let (ident, ty) = args.next().expect("ident/ty for query");
196 let arg_expr = arg_exprs.next().expect("one expr per query");
197 let expr = match arg_expr.as_expr() {
198 Some(expr) => expr,
199 None => {
200 bindings.push(quote_respanned! { arg_expr.span() =>
202 #_fmt::assert_ignorable::<#_fmt::Query, #ty>();
203 });
204
205 return None;
206 }
207 };
208
209 let name = &dynamic.name;
210 add_binding::<fmt::Query>(bindings, &ident, &ty, &expr);
211 Some(match dynamic.trailing {
212 false => quote_spanned! { expr.span() =>
213 #query_arg::NameValue(#name, &#ident as &dyn #uri_display)
214 },
215 true => quote_spanned! { expr.span() =>
216 #query_arg::Value(&#ident as &dyn #uri_display)
217 },
218 })
219 });
220
221 Some(quote!(#_fmt::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])))
222}
223
224pub fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
225 let internal = syn::parse2::<InternalUriParams>(input)?;
227 let (path_exprs, query_exprs, mut fn_args) = extract_exprs(&internal)?;
228
229 let mut bindings = vec![];
230 let path = explode_path(&internal, &mut bindings, path_exprs, &mut fn_args);
231 let query = Optional(explode_query(&internal, &mut bindings, query_exprs, fn_args));
232
233 let prefix = internal.uri_mac.prefix.as_ref()
234 .map(|prefix| quote_spanned!(prefix.span() => .with_prefix(#prefix)));
235
236 let suffix = internal.uri_mac.suffix.as_ref()
237 .map(|suffix| quote_spanned!(suffix.span() => .with_suffix(#suffix)));
238
239 Ok(quote_spanned!(internal.uri_mac.route.path.span() =>
240 #[allow(unused_braces)] {
241 #(#bindings)*
242 let __builder = #_fmt::RouteUriBuilder::new(#path, #query);
243 __builder #prefix #suffix .render()
244 }
245 ))
246}