oauth1_request_derive_ios/
lib.rs

1#![warn(rust_2018_idioms)]
2
3//! This crate provides a derive macro for [`oauth1_request_ios::Request`][Request]:
4//!
5//! [Request]: https://docs.rs/oauth1-request-ios/0.0.1/oauth1_request_ios/trait.Request.html
6//!
7//! ```
8//! # extern crate oauth1_request_ios_ios as oauth;
9//! #[derive(oauth::Request)]
10//! # struct Foo {}
11//! ```
12//!
13//! `oauth1_request_ios` crate re-exports the derive macro if the `derive` feature of the crate
14//! is enabled (which is on by default).
15//! You should use the re-export instead of depending on this crate directly.
16
17#![doc(html_root_url = "https://docs.rs/oauth1-request-derive-ios/0.0.1")]
18
19#[macro_use]
20mod meta;
21
22mod container;
23mod field;
24mod method_body;
25mod util;
26
27use proc_macro2::{Span, TokenStream};
28use proc_macro_crate::FoundCrate;
29use proc_macro_error::{abort, abort_if_dirty, emit_error, proc_macro_error};
30use quote::{quote, quote_spanned};
31use syn::spanned::Spanned;
32use syn::{
33    parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Fields, GenericParam, Generics,
34    Ident,
35};
36
37use self::container::ContainerMeta;
38use self::field::Field;
39use self::method_body::MethodBody;
40
41/// A derive macro for [`oauth1_request_ios::Request`][Request] trait.
42///
43/// [Request]: https://docs.rs/oauth1-request-ios/0.0.1/oauth1_request_ios/trait.Request.html
44///
45/// See the [documentation] on the `oauth1_request_ios` crate.
46///
47/// [documentation]: https://docs.rs/oauth1-request-ios/0.0.1/oauth1_request_ios/derive.Request.html
48#[proc_macro_error]
49#[proc_macro_derive(Request, attributes(oauth1))]
50pub fn derive_oauth1_authorize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
51    let input = parse_macro_input!(input as DeriveInput);
52    expand_derive_oauth1_authorize(input).into()
53}
54
55fn expand_derive_oauth1_authorize(mut input: DeriveInput) -> TokenStream {
56    let name = &input.ident;
57    let span = input.span();
58
59    let meta = ContainerMeta::new(input.attrs);
60
61    let use_oauth1_request_ios = if let Some(krate) = meta.krate {
62        quote! {
63            use #krate as _oauth1_request_ios;
64        }
65    } else {
66        let krate;
67        let krate = match proc_macro_crate::crate_name("oauth1-request-ios") {
68            Ok(FoundCrate::Name(k)) => {
69                krate = k;
70                &*krate
71            }
72            // This is used in `oauth1_request`'s doctests.
73            Ok(FoundCrate::Itself) => {
74                krate = std::env::var("CARGO_CRATE_NAME").unwrap();
75                &*krate
76            }
77            Err(proc_macro_crate::Error::CargoManifestDirNotSet) => "oauth1_request_ios",
78            Err(e) => panic!("{:?}", e),
79        };
80        let krate = Ident::new(krate, Span::call_site());
81        quote! {
82            extern crate #krate as _oauth1_request_ios;
83        }
84    };
85
86    add_trait_bounds(&mut input.generics);
87    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
88
89    proc_macro_error::set_dummy(quote! {
90        const _: () = {
91            #use_oauth1_request_ios
92
93            impl #impl_generics _oauth1_request_ios::Request for #name #ty_generics
94                #where_clause
95            {
96                fn serialize<S>(&self, serializer: S) -> S::Output
97                where
98                    S: _oauth1_request_ios::serializer::Serializer,
99                {
100                    unimplemented!();
101                }
102            }
103        };
104    });
105
106    let fields = match input.data {
107        Data::Struct(DataStruct {
108            fields: Fields::Named(fields),
109            ..
110        }) => fields,
111        _ => abort!(span, "expected a struct with named fields"),
112    };
113
114    let mut fields: Vec<_> = fields.named.into_iter().map(Field::new).collect();
115
116    fields.sort_by_cached_key(|f| f.name().string_value());
117    fields.iter().fold(String::new(), |prev_name, f| {
118        let name = f.name();
119        let (name, span) = (name.string_value(), name.span());
120        if name == prev_name {
121            emit_error!(span, "duplicate parameter \"{}\"", name);
122        }
123        name
124    });
125
126    abort_if_dirty();
127
128    let body = MethodBody::new(&fields);
129
130    quote_spanned! {Span::mixed_site()=>
131        const _: () = {
132            #use_oauth1_request_ios
133
134            #[automatically_derived]
135            impl #impl_generics _oauth1_request_ios::Request for #name #ty_generics
136                #where_clause
137            {
138                // `_S`'s span resolves at call site so prefix it with underscore to avoid conflict.
139                // TODO: Use def-site hygiene once it stabilizes.
140                fn serialize<_S>(&self, mut serializer: _S) -> _S::Output
141                where
142                    _S: _oauth1_request_ios::serializer::Serializer,
143                {
144                    #body
145                }
146            }
147        };
148    }
149}
150
151fn add_trait_bounds(generics: &mut Generics) {
152    for param in &mut generics.params {
153        if let GenericParam::Type(ref mut type_param) = *param {
154            type_param.bounds.push(parse_quote!(::core::fmt::Display));
155        }
156    }
157}