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}