coder/
macros.rs

1//! The macros here are for generating request builders and specifying typed relationships to API
2//! paths and types.
3//!
4//! # Example
5//!
6//! View documentation on each for macro for more information on how each macro works.
7//!
8//! ```rust,ignore
9//! // Common imports used by macros.
10//! imports!();
11//!
12//! // Define request builder types.
13//! new_builder!(
14//!     User,
15//!     Users,
16//! );
17//!
18//! // Request builders we intend to extend must be imported separately.
19//! use crate::client::GetQueryBuilder;
20//!
21//! // Generate `From<GetQueryBuilder>` implementations for our request builders.
22//! from!(
23//!     @GetQuery
24//!         -> User,
25//!         -> Users,
26//! );
27//!
28//! // Implement methods on GetQueryBuilder for accessing our new request builder types.
29//! impl_builder!(
30//!     @GetQuery
31//!         -> users ["users"] -> Users,
32//!         => user  ["users"] -> User = id,
33//! );
34//!
35//! // Mark request builders as executable and specify the return type.
36//! exec!(
37//!     User  -> crate::models::User,
38//!     Users -> Vec<crate::models::User>,
39//! );
40//! ```
41
42/// Necessary imports for when all macros are used within a file.
43macro_rules! imports {
44    () => {
45        #[cfg(feature = "rustls")]
46        type HttpsConnector = hyper_rustls::HttpsConnector<hyper::client::HttpConnector>;
47        #[cfg(feature = "rust-native-tls")]
48        use hyper_tls;
49        #[cfg(feature = "rust-native-tls")]
50        type HttpsConnector = hyper_tls::HttpsConnector<hyper::client::HttpConnector>;
51        use async_trait::async_trait;
52        use hyper::client::Client;
53        use paste::paste;
54        use std::error::Error;
55        use std::sync::Arc;
56        use std::sync::RwLock;
57
58        use $crate::builder::Builder;
59        use $crate::client::ApiResponse;
60        use $crate::client::Executor;
61
62        // This is only used in the `impl_client!` macro.
63        #[allow(unused_imports)]
64        use std::collections::HashMap;
65    };
66}
67
68/// Specifies new request builders. Documentation is passed through.
69///
70/// # Example
71///
72/// ```
73/// new_builder!(
74///     User,
75///     /// Documentation is passed through!
76///     Users,
77/// );
78///
79/// // Expands to ...
80///
81/// pub struct UserBuilder { ... }
82/// unsafe impl Send for UserBuilder {}
83/// /// Documentation is passed through!
84/// pub struct UsersBuilder { ... }
85/// unsafe impl Send for UsersBuilder {}
86/// ```
87macro_rules! new_builder {
88    ($(
89        $(#[$doc:meta])*
90        $i: ident
91    ),* $(,)?) => (
92        $(paste! {
93            $(#[$doc])*
94            pub struct [<$i Builder>] {
95                pub(crate) builder: Result<RwLock<Builder>, Box<dyn Error>>,
96                pub(crate) client: Arc<Client<HttpsConnector>>,
97            }
98            unsafe impl Send for [<$i Builder>] {}
99        })*
100    );
101}
102
103/// Marks a request builder as executable by implementing the `Executor` trait and specifying a
104/// return type. Documentation is passed through.
105///
106/// # Example
107///
108/// ```
109/// exec!(
110/// // builder     return type (implements serde::Deserialize)
111/// //   ||             ||
112/// //   \/             \/
113///     User     -> models::User,      // Returns a struct.
114///     Users    -> Vec<models::User>, // Returns an array of structs.
115///     /// Documentation is passed through!
116///     NoReturn -> (),                // Returns no body.
117/// );
118///
119/// // Expands to ...
120///
121/// impl Executor for UserBuilder {
122///     type T = models::User;
123///
124///     async fn execute(self) -> Result<ApiResponse<models::User>, Box<dyn Error>> { ... }
125/// }
126/// impl Executor for UsersBuilder {
127///     type T = models::User;
128///
129///     async fn execute(self) -> Result<ApiResponse<Vec<models::User>>, Box<dyn Error>> { ... }
130/// }
131/// /// Documentation is passed through!
132/// impl Executor for NoReturnBuilder {
133///     type T = ();
134///
135///     async fn execute(self) -> Result<ApiResponse<()>, Box<dyn Error>> { ... }
136/// }
137/// ```
138macro_rules! exec {
139    ($(
140        $(#[$doc:meta])*
141        $i: ident -> $t: ty
142    ),* $(,)?
143    ) => (
144        paste! {$(
145            $(#[$doc])*
146            #[async_trait]
147            impl Executor for [<$i Builder>] {
148                type T = $t;
149
150                async fn execute(self) -> Result<ApiResponse<Self::T>, Box<dyn Error>> {
151                    let client = self.client;
152                    let builder = self.builder?.into_inner().unwrap();
153                    let req = builder.build();
154                    // dbg!(&req);
155                    let res = client.request(req).await?;
156                    let (parts, body) = res.into_parts();
157
158                    let body = hyper::body::to_bytes(body).await?;
159                    let body = if parts.status.is_success() {
160                        Ok(serde_json::from_slice::<Self::T>(&body)?)
161                    } else {
162                        Err(serde_json::from_slice::<$crate::client::ApiError>(&body)?)
163                    };
164
165                    Ok(ApiResponse {
166                        status_code: parts.status,
167                        headers: parts.headers.into(),
168                        response: body,
169                    })
170                }
171            }
172        )*}
173    );
174}
175
176/// Generates `From<T>` implementations for converting typed request builders into other typed
177/// request builders. The implementations here are used in the `impl_builder!` macro. Documentation
178/// is passed through for target builders.
179///
180/// # Example
181///
182/// ```
183/// from!(
184///     // source builder
185///     //    ||
186///     //    \/
187///     @GetQuery
188///         -> User, // <= target builder
189///         /// Documentation is passed through!
190///         -> Users,
191/// );
192///
193/// // Expands to ...
194///
195/// impl From<GetQueryBuilder> for UserBuilder {
196///     fn from(f: GetQueryBuilder) -> Self { ... }
197/// }
198/// /// Documentation is passed through!
199/// impl From<GetQueryBuilder> for UsersBuilder {
200///     fn from(f: GetQueryBuilder) -> Self { ... }
201/// }
202/// ```
203macro_rules! from {
204    ($(@$f: ident
205        $(
206            $(#[$doc:meta])*
207            -> $t: ident
208        ),+ $(,)?
209    )+) => {
210        $($(paste! {
211            $(#[$doc])*
212            impl From<[<$f Builder>]> for [<$t Builder>] {
213                fn from(f: [<$f Builder>]) -> Self {
214                    Self {
215                        builder: f.builder,
216                        client: f.client,
217                    }
218                }
219            }
220        })+)+
221    };
222}
223
224/// Generates code for extending request builders into other request builders. All builders must be
225/// created by the `new_builder!` macro and have conversions specified by the `from!` macro.
226///
227/// # Example
228///
229/// ```
230/// impl_builder!(
231///     // source builder
232///     //    ||
233///     //    \/
234///     @GetQuery
235///         // There are two different types of impls we can generate:
236///         //   1. `->` which generates an impl requiring no route variable.
237///         //   2. `=>` which generates an impl requiring a route variable.
238///         //       The route variable will be appended to the provided route path.
239///         //
240///         // method name      new builder
241///         //  ||   route path     ||
242///         //  ||       ||         ||  route variable name
243///         //  \/       \/         \/          ||
244///         -> users ["users"] -> Users,     // ||
245///         /// Docs are passed through too!    \/
246///         => user  ["users"] -> User       = id,
247/// );
248///
249/// // Expands to ...
250///
251/// impl GetQueryBuilder {
252///     pub fn users(mut self) -> UsersBuilder { ... }
253///     /// Docs are passed through too!
254///     pub fn user<T: ToString>(mut self, id: T) -> UserBuilder { ... }
255/// }
256/// ```
257macro_rules! impl_builder {
258    // This first line should contain @ and the struct we are implementing from, like @GetQuery.
259    // The struct should have been generated from the new_builder! macro.
260    ($(@$i: ident
261       $(
262           $(#[$doc:meta])*
263            // Case 1
264            // This case is for methods that don't need a route variable such, as getting all
265            // users.
266            // The syntax looks like: `-> <method name> [<route path>] -> <builder name>`.
267            // Builder name should be a struct generated by the new_builder! macro.
268            $(-> $fn1:ident [$($p1:literal)?] -> $t1:ident)?
269
270            // Case 2
271            // This case is for methods that need a route variable such as getting a user by id.
272            // The syntax looks like: `=> <method name> [<route path>] -> <builder name> = <path variable name>`
273            // Builder name should be a struct generated by the new_builder! macro.
274            $(=> $fn2:ident [$($p2:literal)?] -> $t2:ident = $e2:ident)?
275
276            $(?> $fn3:ident [$q3:literal] -> $n3:ident: $t3:ty)?
277        ),*
278    )+)=> (
279        $(paste! {
280            impl [<$i Builder>] {
281            $(
282                $(#[$doc])*
283                // Case 1
284                $(
285                    pub fn $fn1(mut self) -> [<$t1 Builder>] {
286                        join_path!(self, &[$($p1)?]);
287                        self.into()
288                    }
289                )?
290                // Case 2
291                $(
292                    pub fn $fn2<T: ToString>(mut self, $e2: T) -> [<$t2 Builder>] {
293                        join_path!(self, &[$($p2,)? &$e2.to_string()]);
294                        self.into()
295                    }
296                )?
297                // Case 3
298                $(
299                    pub fn $fn3(mut self, $n3: $t3) -> [<$i Builder>] {
300                        join_query!(self, $q3, $n3);
301                        self.into()
302                    }
303                )?
304            )*
305            }
306        })+
307    );
308}
309
310macro_rules! impl_client {
311    ($(
312       $(#[$doc:meta])*
313        $(-> $fn:ident [$p:literal] -> $t:ident)?
314    ),*)=> (
315        impl $crate::client::Coder {
316            $(paste! {
317                $(#[$doc])*
318                $(
319                    pub fn $fn(&self) -> [<$t Builder>] {
320                        let mut b = [<$t Builder>] {
321                            builder: self.new_request().map(|r| RwLock::new(Builder{
322                                query: HashMap::new(),
323                                url: self.url.clone(),
324                                req: r,
325
326                            })),
327                            client: Arc::clone(&self.client),
328                        };
329                        join_path!(b, &[$p]);
330                        b
331                    }
332                )?
333            })*
334        }
335    );
336}
337
338macro_rules! join_path {
339    ($e: ident, $p: expr) => {
340        if $e.builder.is_ok() {
341            // We've checked that this works
342            let inner = $e.builder.unwrap();
343            inner
344                .write()
345                .unwrap()
346                .url
347                .path_segments_mut()
348                .unwrap()
349                .extend($p);
350
351            $e.builder = Ok(inner);
352        }
353    };
354}
355
356macro_rules! join_query {
357    ($e: ident, $k: expr, $v: expr) => {
358        if $e.builder.is_ok() {
359            // We've checked that this works
360            let inner = $e.builder.unwrap();
361            inner.write().unwrap().query.insert($k, $v.to_string());
362
363            $e.builder = Ok(inner);
364        }
365    };
366}
367
368macro_rules! id_string {
369    ($($name:ident),*) => {
370        $(
371            impl std::ops::Deref for $name {
372                type Target = String;
373
374                fn deref(&self) -> &Self::Target {
375                    &self.0
376                }
377            }
378
379            impl std::convert::From<String> for $name {
380                fn from(i: String) -> Self {
381                    Self(i)
382                }
383            }
384
385            impl std::convert::From<&str> for $name {
386                fn from(i: &str) -> Self {
387                    Self(i.to_string())
388                }
389            }
390        )*
391    }
392}