1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#![warn(rust_2018_idioms)]

//! This crate provides a derive macro for [`oauth1_request::Request`][Request]:
//!
//! [Request]: https://docs.rs/oauth1-request/0.5/oauth1_request/trait.Request.html
//!
//! ```
//! # extern crate oauth1_request as oauth;
//! #[derive(oauth::Request)]
//! # struct Foo {}
//! ```
//!
//! `oauth1_request` crate re-exports the derive macro if the `derive` feature of the crate
//! is enabled (which is on by default).
//! You should use the re-export instead of depending on this crate directly.

#![doc(html_root_url = "https://docs.rs/oauth1-request-derive/0.5.0")]

#[macro_use]
mod meta;

mod container;
mod field;
mod method_body;
mod util;

use proc_macro2::{Span, TokenStream};
use proc_macro_crate::FoundCrate;
use proc_macro_error::{abort, abort_if_dirty, emit_error, proc_macro_error};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
    parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Fields, GenericParam, Generics,
    Ident,
};

use self::container::ContainerMeta;
use self::field::Field;
use self::method_body::MethodBody;

/// A derive macro for [`oauth1_request::Request`][Request] trait.
///
/// [Request]: https://docs.rs/oauth1-request/0.5/oauth1_request/trait.Request.html
///
/// See the [documentation] on the `oauth1_request` crate.
///
/// [documentation]: https://docs.rs/oauth1-request/0.5/oauth1_request/derive.Request.html
#[proc_macro_error]
#[proc_macro_derive(Request, attributes(oauth1))]
pub fn derive_oauth1_authorize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    expand_derive_oauth1_authorize(input).into()
}

fn expand_derive_oauth1_authorize(mut input: DeriveInput) -> TokenStream {
    let name = &input.ident;
    let span = input.span();

    let meta = ContainerMeta::new(input.attrs);

    let use_oauth1_request = if let Some(krate) = meta.krate {
        quote! {
            use #krate as _oauth1_request;
        }
    } else {
        let krate;
        let krate = match proc_macro_crate::crate_name("oauth1-request") {
            Ok(FoundCrate::Name(k)) => {
                krate = k;
                &*krate
            }
            // This is used in `oauth1_request`'s doctests.
            Ok(FoundCrate::Itself) => {
                krate = std::env::var("CARGO_CRATE_NAME").unwrap();
                &*krate
            }
            Err(proc_macro_crate::Error::CargoManifestDirNotSet) => "oauth1_request",
            Err(e) => Err(e).unwrap(),
        };
        let krate = Ident::new(krate, Span::call_site());
        quote! {
            extern crate #krate as _oauth1_request;
        }
    };

    add_trait_bounds(&mut input.generics);
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    proc_macro_error::set_dummy(quote! {
        const _: () = {
            #use_oauth1_request

            impl #impl_generics _oauth1_request::Request for #name #ty_generics
                #where_clause
            {
                fn serialize<S>(&self, serializer: S) -> S::Output
                where
                    S: _oauth1_request::serializer::Serializer,
                {
                    unimplemented!();
                }
            }
        };
    });

    let fields = match input.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(fields),
            ..
        }) => fields,
        _ => abort!(span, "expected a struct with named fields"),
    };

    let mut fields: Vec<_> = fields.named.into_iter().map(Field::new).collect();

    fields.sort_by_cached_key(|f| f.name().string_value());
    fields.iter().fold(String::new(), |prev_name, f| {
        let name = f.name();
        let (name, span) = (name.string_value(), name.span());
        if name == prev_name {
            emit_error!(span, "duplicate parameter \"{}\"", name);
        }
        name
    });

    abort_if_dirty();

    let body = MethodBody::new(&fields);

    quote_spanned! {Span::mixed_site()=>
        const _: () = {
            #use_oauth1_request

            #[automatically_derived]
            impl #impl_generics _oauth1_request::Request for #name #ty_generics
                #where_clause
            {
                // `_S`'s span resolves at call site so prefix it with underscore to avoid conflict.
                // TODO: Use def-site hygiene once it stabilizes.
                fn serialize<_S>(&self, mut serializer: _S) -> _S::Output
                where
                    _S: _oauth1_request::serializer::Serializer,
                {
                    #body
                }
            }
        };
    }
}

fn add_trait_bounds(generics: &mut Generics) {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(::core::fmt::Display));
        }
    }
}