1
extern crate proc_macro;
2

3
use std::collections::HashSet;
4
use std::convert::TryFrom;
5

6
use proc_macro::TokenStream;
7
use proc_macro2::{Span, TokenStream as TokenStream2};
8
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
9
use syn::{parse_macro_input, AttributeArgs, Ident, NestedMeta};
10

11
enum ResourceType {
12
    Async,
13
    Sync,
14
}
15

16
impl ToTokens for ResourceType {
17 0
    fn to_tokens(&self, stream: &mut TokenStream2) {
18 0
        let ident = format_ident!("to");
19 0
        stream.append(ident);
20
    }
21
}
22

23
macro_rules! method_type {
24
    (
25
        $($variant:ident, $upper:ident,)+
26
    ) => {
27
        #[derive(Debug, PartialEq, Eq, Hash)]
28
        pub enum MethodType {
29
            $(
30
                $variant,
31
            )+
32
        }
33

34
        impl MethodType {
35
            fn as_str(&self) -> &'static str {
36
                match self {
37
                    $(Self::$variant => stringify!($variant),)+
38
                }
39
            }
40

41
            fn parse(method: &str) -> Result<Self, String> {
42
                match method {
43
                    $(stringify!($upper) => Ok(Self::$variant),)+
44
                    _ => Err(format!("Unexpected HTTP method: `{}`", method)),
45
                }
46
            }
47
        }
48
    };
49
}
50

51
method_type! {
52
    Get,       GET,
53
    Post,      POST,
54
    Put,       PUT,
55
    Delete,    DELETE,
56
    Head,      HEAD,
57
    Connect,   CONNECT,
58
    Options,   OPTIONS,
59
    Trace,     TRACE,
60
    Patch,     PATCH,
61
}
62

63
impl ToTokens for MethodType {
64 0
    fn to_tokens(&self, stream: &mut TokenStream2) {
65 0
        let ident = Ident::new(self.as_str(), Span::call_site());
66 0
        stream.append(ident);
67
    }
68
}
69

70
impl TryFrom<&syn::LitStr> for MethodType {
71
    type Error = syn::Error;
72

73 0
    fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> {
74 0
        Self::parse(value.value().as_str())
75 0
            .map_err(|message| syn::Error::new_spanned(value, message))
76
    }
77
}
78

79
struct Args {
80
    path: syn::LitStr,
81
    guards: Vec<Ident>,
82
    wrappers: Vec<syn::Type>,
83
    methods: HashSet<MethodType>,
84
}
85

86
impl Args {
87 0
    fn new(args: AttributeArgs, method: Option<MethodType>) -> syn::Result<Self> {
88 0
        let mut path = None;
89 0
        let mut guards = Vec::new();
90 0
        let mut wrappers = Vec::new();
91 0
        let mut methods = HashSet::new();
92

93 0
        let is_route_macro = method.is_none();
94 0
        if let Some(method) = method {
95 0
            methods.insert(method);
96
        }
97

98 0
        for arg in args {
99 0
            match arg {
100 0
                NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
101 0
                    None => {
102 0
                        path = Some(lit);
103
                    }
104 0
                    _ => {
105 0
                        return Err(syn::Error::new_spanned(
106 0
                            lit,
107 0
                            "Multiple paths specified! Should be only one!",
108
                        ));
109
                    }
110
                },
111 0
                NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
112 0
                    if nv.path.is_ident("guard") {
113 0
                        if let syn::Lit::Str(lit) = nv.lit {
114 0
                            guards.push(Ident::new(&lit.value(), Span::call_site()));
115
                        } else {
116 0
                            return Err(syn::Error::new_spanned(
117 0
                                nv.lit,
118 0
                                "Attribute guard expects literal string!",
119
                            ));
120
                        }
121 0
                    } else if nv.path.is_ident("wrap") {
122 0
                        if let syn::Lit::Str(lit) = nv.lit {
123 0
                            wrappers.push(lit.parse()?);
124
                        } else {
125 0
                            return Err(syn::Error::new_spanned(
126 0
                                nv.lit,
127 0
                                "Attribute wrap expects type",
128
                            ));
129
                        }
130 0
                    } else if nv.path.is_ident("method") {
131 0
                        if !is_route_macro {
132 0
                            return Err(syn::Error::new_spanned(
133 0
                                &nv,
134 0
                                "HTTP method forbidden here. To handle multiple methods, use `route` instead",
135
                            ));
136 0
                        } else if let syn::Lit::Str(ref lit) = nv.lit {
137 0
                            let method = MethodType::try_from(lit)?;
138 0
                            if !methods.insert(method) {
139 0
                                return Err(syn::Error::new_spanned(
140 0
                                    &nv.lit,
141 0
                                    &format!(
142 0
                                        "HTTP method defined more than once: `{}`",
143 0
                                        lit.value()
144
                                    ),
145
                                ));
146
                            }
147
                        } else {
148 0
                            return Err(syn::Error::new_spanned(
149 0
                                nv.lit,
150 0
                                "Attribute method expects literal string!",
151
                            ));
152
                        }
153
                    } else {
154 0
                        return Err(syn::Error::new_spanned(
155 0
                            nv.path,
156 0
                            "Unknown attribute key is specified. Allowed: guard, method and wrap",
157
                        ));
158
                    }
159
                }
160 0
                arg => {
161 0
                    return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
162
                }
163
            }
164
        }
165 0
        Ok(Args {
166 0
            path: path.unwrap(),
167 0
            guards,
168 0
            wrappers,
169 0
            methods,
170
        })
171
    }
172
}
173

174
pub struct Route {
175
    name: syn::Ident,
176
    args: Args,
177
    ast: syn::ItemFn,
178
    resource_type: ResourceType,
179
}
180

181
fn guess_resource_type(typ: &syn::Type) -> ResourceType {
182
    let mut guess = ResourceType::Sync;
183

184
    if let syn::Type::ImplTrait(typ) = typ {
185
        for bound in typ.bounds.iter() {
186
            if let syn::TypeParamBound::Trait(bound) = bound {
187
                for bound in bound.path.segments.iter() {
188
                    if bound.ident == "Future" {
189
                        guess = ResourceType::Async;
190
                        break;
191
                    } else if bound.ident == "Responder" {
192
                        guess = ResourceType::Sync;
193
                        break;
194
                    }
195
                }
196
            }
197
        }
198
    }
199

200
    guess
201
}
202

203
impl Route {
204 0
    pub fn new(
205
        args: AttributeArgs,
206
        input: TokenStream,
207
        method: Option<MethodType>,
208
    ) -> syn::Result<Self> {
209 0
        if args.is_empty() {
210 0
            return Err(syn::Error::new(
211 0
                Span::call_site(),
212 0
                format!(
213 0
                    r#"invalid service definition, expected #[{}("<some path>")]"#,
214 0
                    method
215 0
                        .map(|it| it.as_str())
216 0
                        .unwrap_or("route")
217 0
                        .to_ascii_lowercase()
218
                ),
219
            ));
220
        }
221 0
        let ast: syn::ItemFn = syn::parse(input)?;
222 0
        let name = ast.sig.ident.clone();
223

224 0
        let args = Args::new(args, method)?;
225 0
        if args.methods.is_empty() {
226 0
            return Err(syn::Error::new(
227 0
                Span::call_site(),
228 0
                "The #[route(..)] macro requires at least one `method` attribute",
229
            ));
230
        }
231

232 0
        let resource_type = if ast.sig.asyncness.is_some() {
233 0
            ResourceType::Async
234
        } else {
235 0
            match ast.sig.output {
236 0
                syn::ReturnType::Default => {
237 0
                    return Err(syn::Error::new_spanned(
238 0
                        ast,
239 0
                        "Function has no return type. Cannot be used as handler",
240
                    ));
241
                }
242 0
                syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
243
            }
244
        };
245

246 0
        Ok(Self {
247 0
            name,
248 0
            args,
249 0
            ast,
250 0
            resource_type,
251
        })
252
    }
253
}
254

255
impl ToTokens for Route {
256 0
    fn to_tokens(&self, output: &mut TokenStream2) {
257 0
        let Self {
258 0
            name,
259 0
            ast,
260 0
            args:
261 0
                Args {
262 0
                    path,
263 0
                    guards,
264 0
                    wrappers,
265 0
                    methods,
266
                },
267 0
            resource_type,
268 0
        } = self;
269 0
        let resource_name = name.to_string();
270 0
        let method_guards = {
271 0
            let mut others = methods.iter();
272
            // unwrapping since length is checked to be at least one
273 0
            let first = others.next().unwrap();
274

275 0
            if methods.len() > 1 {
276 0
                quote! {
277 0
                    .guard(
278 0
                        actix_web::guard::Any(actix_web::guard::#first())
279 0
                            #(.or(actix_web::guard::#others()))*
280
                    )
281
                }
282
            } else {
283 0
                quote! {
284 0
                    .guard(actix_web::guard::#first())
285
                }
286
            }
287
        };
288

289 0
        let stream = quote! {
290 0
            #[allow(non_camel_case_types, missing_docs)]
291 0
            pub struct #name;
292

293 0
            impl actix_web::dev::HttpServiceFactory for #name {
294 0
                fn register(self, __config: &mut actix_web::dev::AppService) {
295 0
                    #ast
296 0
                    let __resource = actix_web::Resource::new(#path)
297 0
                        .name(#resource_name)
298 0
                        #method_guards
299 0
                        #(.guard(actix_web::guard::fn_guard(#guards)))*
300 0
                        #(.wrap(#wrappers))*
301 0
                        .#resource_type(#name);
302

303 0
                    actix_web::dev::HttpServiceFactory::register(__resource, __config)
304
                }
305
            }
306
        };
307

308 0
        output.extend(stream);
309
    }
310
}
311

312
pub(crate) fn with_method(
313
    method: Option<MethodType>,
314
    args: TokenStream,
315
    input: TokenStream,
316
) -> TokenStream {
317
    let args = parse_macro_input!(args as syn::AttributeArgs);
318
    match Route::new(args, input, method) {
319
        Ok(route) => route.into_token_stream().into(),
320
        Err(err) => err.to_compile_error().into(),
321
    }
322
}

Read our documentation on viewing source code .

Loading