rocket_codegen/bang/
uri.rs

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>,             // path exprs
44        impl Iterator<Item = &ArgExpr>,          // query exprs
45        impl Iterator<Item = (&Ident, &Type)>,   // types for both path || query
46    )>
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                // Force a typecheck for the `Ignoreable` trait.
201                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    // Parse the internal invocation and the user's URI param expressions.
226    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}