Skip to main content

esi_openapi/
macros.rs

1/// Create a function for calling a single endpoint
2/// with a GET request.
3///
4/// # Example
5/// ```rust,no_run
6/// # use esi_openapi::prelude::*;
7/// # use esi_openapi::api_get;
8/// pub struct SomeGroup<'a> {
9///     pub(crate) esi: &'a Esi,
10/// }
11///
12/// impl SomeGroup<'_> {
13///
14///     api_get!(
15///         /// Docs for the generated function
16///         function_name,
17///         "some_operation_id",
18///         RequestType::Public,
19///         Vec<u64>,
20///     );
21///
22/// }
23/// # fn main() {}
24/// ```
25///
26/// ## Result:
27///
28/// ```rust,ignore
29/// /// Docs for the generated function
30/// pub async fn function_name(&self) -> EsiResult<Vec<u64>> {
31///     let path = self.esi.get_endpoint_for_op_id("some_operation_id")?;
32///     self.esi
33///         .query("GET", RequestType::Public, &path, None, None)
34///         .await
35/// }
36/// ```
37///
38/// Additionally, this macro supports path replacements to insert variables
39/// into the path from ESI.
40///
41/// # Example
42///
43/// ```rust,no_run
44/// # use esi_openapi::prelude::*;
45/// # use esi_openapi::api_get;
46/// pub struct SomeGroup<'a> {
47///     pub(crate) esi: &'a Esi,
48/// }
49///
50/// impl SomeGroup<'_> {
51///
52///     api_get!(
53///         /// Docs for the generated function
54///         function_name,
55///         "some_operation_id",
56///         RequestType::Public,
57///         Vec<u64>,
58///         (alliance_id: i64) => "{alliance_id}"
59///     );
60///
61/// }
62/// # fn main() {}
63/// ```
64/// ## Result:
65///
66/// ```rust,ignore
67/// /// Docs for the generated function
68/// pub async fn function_name(&self, alliance_id: i64) -> EsiResult<Vec<u64>> {
69///     let path = self.esi.get_endpoint_for_op_id("some_operation_id")?
70///         .replace("{alliance_id}", &alliance_id.to_string());
71///     self.esi
72///         .query("GET", RequestType::Public, &path, None, None)
73///         .await
74/// }
75/// ```
76///
77/// Finally, there is support for required and optional query params. These are different from path
78/// parameters: in 'markets/{region_id}/orders?page=1', region_id is a path parameter and page is a
79/// query parameter. Note that in the macro invocation, query parameters are separated from path
80/// parameters with a semicolon, and that optional query parameters always follow required ones.
81/// See [crate::groups::MarketGroup] for sample macro calls.
82///
83/// # Example
84///
85/// ```rust,no_run
86/// # use esi_openapi::prelude::*;
87/// # use esi_openapi::api_get;
88/// pub struct SomeGroup<'a> {
89///     pub(crate) esi: &'a Esi,
90/// }
91///
92/// impl SomeGroup<'_> {
93///
94///     api_get!(
95///         /// Docs for the generated function
96///         function_name,
97///         "some_operation_id",
98///         RequestType::Public,
99///         Vec<u64>,
100///         (region_id: u64) => "{region_id}";
101///         (page: i32) => "page";
102///         Optional(order_type: bool) => "order_type"
103///     );
104///
105/// }
106/// # fn main() {}
107/// ```
108/// ## Result:
109///
110/// ```rust,ignore
111/// /// Docs for the generated function
112/// pub async fn function_name(&self, region_id: u64, page: i32, order_type: Option<bool>) -> EsiResult<Vec<u64>> {
113///     let path = self.esi.get_endpoint_for_op_id("some_operation_id")?
114///         .replace("{region_id}", &region_id.to_string());
115///     let params = vec![
116///         ("page", page.to_string()),
117///     ]
118///     let mut params = params;
119///     if let Some(order_type) = order_type {
120///         params.push(("order_type", order_type.to_string()))
121///     }
122///     let params: Vec<(&str, &str)> = params.iter().map(|(a, b)| (*a, &**b)).collect();
123///     self.esi
124///         .query("GET", RequestType::Public, &path, Some(&params), None)
125///         .await
126/// }
127/// ```
128///
129/// # Extended query parameters
130///
131/// `api_get!`, `api_post!`, `api_put!` and `api_delete!` also accept query
132/// parameters tagged with a kind, after a `;` that follows the path parameters:
133///
134/// - `Required(name: T)`: a required value, sent as `name=value`
135/// - `Optional(name: T)`: the function takes an `Option<T>`
136/// - `Many(name: &[T])`: a required list, sent as repeated keys (`name=1&name=2`)
137/// - `OptionalMany(name: &[T])`: the function takes an `Option<&[T]>`
138///
139/// `api_post!` and `api_put!` take the body after a second `;`.
140///
141/// # Example
142///
143/// ```rust,no_run
144/// # use esi_openapi::prelude::*;
145/// # use esi_openapi::{api_get, api_post};
146/// pub struct SomeGroup<'a> {
147///     pub(crate) esi: &'a Esi,
148/// }
149///
150/// impl SomeGroup<'_> {
151///
152///     api_get!(
153///         /// Docs for the generated function
154///         list_things,
155///         "some_operation_id",
156///         RequestType::Authenticated,
157///         Vec<u64>,
158///         (character_id: i64) => "{character_id}";
159///         OptionalMany(labels: &[i64]) => "labels",
160///         Optional(last_id: i64) => "last_id"
161///     );
162///
163///     api_post!(
164///         /// Docs for the generated function
165///         add_things,
166///         "some_other_operation_id",
167///         RequestType::Authenticated,
168///         Vec<i64>,
169///         (character_id: i64) => "{character_id}";
170///         Required(standing: f64) => "standing",
171///         Optional(watched: bool) => "watched";
172///         ids: &[i64]
173///     );
174///
175/// }
176/// # fn main() {}
177/// ```
178#[macro_export]
179macro_rules! api_get {
180    (
181        $(#[$m:meta])*
182        $fn_name:ident,
183        $op_id:literal,
184        $visibility:expr,
185        $ret_type:ty,
186        $( ($param:ident: $param_t:ty) => $replace:literal ),*
187    ) => {
188        $(#[$m])*
189        pub async fn $fn_name(&self, $( $param: $param_t, )*) -> EsiResult<$ret_type> {
190            let path = self
191                .esi
192                .get_endpoint_for_op_id($op_id)?
193                $(
194                    .replace($replace, &$param.to_string())
195                )*;
196            self.esi.
197                query("GET", $visibility, &path, None, None)
198                .await
199        }
200    };
201    (
202        $(#[$m:meta])*
203        $fn_name:ident,
204        $op_id:literal,
205        $visibility:expr,
206        $ret_type:ty,
207        $( ($param:ident: $param_t:ty) => $replace:literal ),*
208        $( ; $( ($qparam:ident: $qparam_t:ty) => $qreplace:literal ),+ )?
209        $( ; $( Optional($opt_qparam:ident: $opt_qparam_t:ty) => $opt_qreplace:literal ),+ )?
210    ) => {
211        $(#[$m])*
212        pub async fn $fn_name(
213            &self,
214            $( $param: $param_t, )*
215            $($( $qparam: $qparam_t, )*)?
216            $($( $opt_qparam: Option<$opt_qparam_t>, )*)?
217        ) -> EsiResult<$ret_type> {
218            let path = self
219                .esi
220                .get_endpoint_for_op_id($op_id)?
221                $(
222                    .replace($replace, &$param.to_string())
223                )*;
224            let params = vec![
225                $($(
226                    ($qreplace, $qparam.to_string()),
227                )+)?
228            ];
229            $(
230                let mut params = params; // avoids unnecessary 'mut' warning
231                $(
232                    if let Some($opt_qparam) = $opt_qparam {
233                        params.push(($opt_qreplace, $opt_qparam.to_string()));
234                    }
235                )+
236            )?
237            let params: Vec<(&str, &str)> = params.iter().map(|(a, b)| (*a, &**b)).collect();
238            self.esi.
239                query("GET", $visibility, &path, Some(&params), None)
240                .await
241        }
242    };
243    (
244        $(#[$m:meta])*
245        $fn_name:ident,
246        $op_id:literal,
247        $visibility:expr,
248        $ret_type:ty,
249        $( ($param:ident: $param_t:ty) => $replace:literal ),*
250        ; $( $qkind:ident($qparam:ident: $qparam_t:ty) => $qkey:literal ),+ $(,)?
251    ) => {
252        $crate::__esi_endpoint!(
253            $(#[$m])*
254            $fn_name, $op_id, $visibility, $ret_type, "GET",
255            [ $( ($param: $param_t) => $replace ),* ]
256            [ $( $qkind($qparam: $qparam_t) => $qkey ),+ ]
257            [ ]
258        );
259    };
260}
261
262/// Internal: the type of a query parameter, given its kind.
263#[doc(hidden)]
264#[macro_export]
265macro_rules! __esi_query_type {
266    (Required, $t:ty) => { $t };
267    (Optional, $t:ty) => { Option<$t> };
268    (Many, $t:ty) => { $t };
269    (OptionalMany, $t:ty) => { Option<$t> };
270}
271
272/// Internal: add a query parameter to the list of `(key, value)` pairs.
273/// List parameters are sent as repeated keys (`labels=1&labels=2`).
274#[doc(hidden)]
275#[macro_export]
276macro_rules! __esi_push_query {
277    ($params:ident, Required, $name:ident, $key:literal) => {
278        $params.push(($key, $name.to_string()));
279    };
280    ($params:ident, Optional, $name:ident, $key:literal) => {
281        if let Some(value) = $name {
282            $params.push(($key, value.to_string()));
283        }
284    };
285    ($params:ident, Many, $name:ident, $key:literal) => {
286        for value in $name.iter() {
287            $params.push(($key, value.to_string()));
288        }
289    };
290    ($params:ident, OptionalMany, $name:ident, $key:literal) => {
291        if let Some(list) = $name {
292            for value in list.iter() {
293                $params.push(($key, value.to_string()));
294            }
295        }
296    };
297}
298
299/// Internal: serialize the request body, if the endpoint has one.
300#[doc(hidden)]
301#[macro_export]
302macro_rules! __esi_body {
303    () => {
304        None::<String>
305    };
306    ($body:ident) => {
307        Some(serde_json::to_string($body)?)
308    };
309}
310
311/// Internal: builds an endpoint function with path parameters, typed query
312/// parameters (`Required`, `Optional`, `Many` or `OptionalMany`) and an
313/// optional body. Used by the extended forms of `api_get!`, `api_post!`,
314/// `api_put!` and `api_delete!`.
315#[doc(hidden)]
316#[macro_export]
317macro_rules! __esi_endpoint {
318    (
319        $(#[$m:meta])*
320        $fn_name:ident, $op_id:literal, $visibility:expr, $ret_type:ty, $method:literal,
321        [ $( ($param:ident: $param_t:ty) => $replace:literal ),* ]
322        [ $( $qkind:ident($qparam:ident: $qparam_t:ty) => $qkey:literal ),* ]
323        [ $( $body_param:ident: $body_t:ty )? ]
324    ) => {
325        $(#[$m])*
326        #[allow(clippy::vec_init_then_push)]
327        pub async fn $fn_name(
328            &self,
329            $( $param: $param_t, )*
330            $( $qparam: $crate::__esi_query_type!($qkind, $qparam_t), )*
331            $( $body_param: $body_t, )?
332        ) -> EsiResult<$ret_type> {
333            let path = self
334                .esi
335                .get_endpoint_for_op_id($op_id)?
336                $(
337                    .replace($replace, &$param.to_string())
338                )*;
339            #[allow(unused_mut)]
340            let mut params: Vec<(&str, String)> = Vec::new();
341            $(
342                $crate::__esi_push_query!(params, $qkind, $qparam, $qkey);
343            )*
344            let params: Vec<(&str, &str)> = params.iter().map(|(a, b)| (*a, &**b)).collect();
345            let body: Option<String> = $crate::__esi_body!($($body_param)?);
346            self.esi
347                .query($method, $visibility, &path, Some(&params), body.as_deref())
348                .await
349        }
350    };
351}
352
353/// Create a function for calling a single endpoint
354/// with a POST request.
355///
356/// Follows the structure of the `api_get!` macro, with the
357/// addition of taking an additional pair of `ident` and `ty`
358/// to name and type the data that will be passed to
359/// `serde_json::to_string` for serializing for setting the
360/// request's body.
361///
362/// # Example
363///
364/// ```rust,no_run
365/// # use esi_openapi::prelude::*;
366/// # use esi_openapi::api_post;
367/// pub struct SomeGroup<'a> {
368///     pub(crate) esi: &'a Esi,
369/// }
370///
371/// impl SomeGroup<'_> {
372///
373///     api_post!(
374///         /// Docs for the generated function
375///         function_name,
376///         "some_operation_id",
377///         RequestType::Public,
378///         Vec<u64>,
379///         (alliance_id: i64) => "{alliance_id}",
380///         ids: &[u64],
381///     );
382///
383/// }
384/// # fn main() {}
385/// ```
386/// ## Result:
387///
388/// ```rust,ignore
389/// /// Docs for the generated function
390/// pub async fn function_name(&self, alliance_id: i64, ids: &[u64]) -> EsiResult<Vec<u64>> {
391///     let path = self.esi.get_endpoint_for_op_id("some_operation_id")?
392///         .replace("{alliance_id}", &alliance_id.to_string());
393///     let body = serde_json::to_string(ids);
394///     self.esi
395///         .query("GET", RequestType::Public, &path, None, Some(&body))
396///         .await
397/// }
398/// ```
399#[macro_export]
400macro_rules! api_post {
401    (
402        $(#[$m:meta])*
403        $fn_name:ident,
404        $op_id:literal,
405        $visibility:expr,
406        $ret_type:ty,
407        $( ($param:ident: $param_t:ty) => $replace:literal ),*,
408        $body_param:ident: $param_type:ty,
409    ) => {
410        $(#[$m])*
411        pub async fn $fn_name(&self, $( $param: $param_t, )* $body_param: $param_type) -> EsiResult<$ret_type> {
412            let path = self
413                .esi
414                .get_endpoint_for_op_id($op_id)?
415                $(
416                    .replace($replace, &$param.to_string())
417                )*;
418            let body = serde_json::to_string($body_param)?;
419            self.esi.
420                query("POST", $visibility, &path, None, Some(&body))
421                .await
422        }
423    };
424    // A POST whose body is an array with a maximum length: the slice is split into
425    // requests of at most `$max` items and the answers are joined in order.
426    // `path params ; Chunked(body: &[T], max)`.
427    (
428        $(#[$m:meta])*
429        $fn_name:ident,
430        $op_id:literal,
431        $visibility:expr,
432        $ret_type:ty,
433        $( ($param:ident: $param_t:ty) => $replace:literal ),*
434        ; Chunked($body_param:ident: $body_t:ty, $max:literal) $(,)?
435    ) => {
436        $(#[$m])*
437        ///
438        /// The spec allows at most
439        #[doc = concat!(stringify!($max), " items per request; longer lists are split into several requests,")]
440        /// sent concurrently, and the results are joined in order. An empty list sends nothing.
441        pub async fn $fn_name(&self, $( $param: $param_t, )* $body_param: $body_t) -> EsiResult<$ret_type> {
442            let path = self
443                .esi
444                .get_endpoint_for_op_id($op_id)?
445                $(
446                    .replace($replace, &$param.to_string())
447                )*;
448            self.esi
449                .post_chunked($visibility, &path, $body_param, $max)
450                .await
451        }
452    };
453    (
454        $(#[$m:meta])*
455        $fn_name:ident,
456        $op_id:literal,
457        $visibility:expr,
458        $ret_type:ty,
459        $( ($param:ident: $param_t:ty) => $replace:literal ),*
460        ; $( $qkind:ident($qparam:ident: $qparam_t:ty) => $qkey:literal ),*
461        ; $body_param:ident: $body_t:ty $(,)?
462    ) => {
463        $crate::__esi_endpoint!(
464            $(#[$m])*
465            $fn_name, $op_id, $visibility, $ret_type, "POST",
466            [ $( ($param: $param_t) => $replace ),* ]
467            [ $( $qkind($qparam: $qparam_t) => $qkey ),* ]
468            [ $body_param: $body_t ]
469        );
470    };
471    // A POST request without a body: `path params ; NoBody`.
472    (
473        $(#[$m:meta])*
474        $fn_name:ident,
475        $op_id:literal,
476        $visibility:expr,
477        $ret_type:ty,
478        $( ($param:ident: $param_t:ty) => $replace:literal ),*
479        ; NoBody $(,)?
480    ) => {
481        $crate::__esi_endpoint!(
482            $(#[$m])*
483            $fn_name, $op_id, $visibility, $ret_type, "POST",
484            [ $( ($param: $param_t) => $replace ),* ]
485            [ ]
486            [ ]
487        );
488    };
489}
490
491/// Create a function for calling a single endpoint
492/// with a PUT request.
493///
494/// Follows the structure of the `api_post!` macro: path parameters,
495/// followed by the data that will be passed to `serde_json::to_string`
496/// to set the request's body.
497///
498/// # Example
499///
500/// ```rust,no_run
501/// # use esi_openapi::prelude::*;
502/// # use esi_openapi::api_put;
503/// pub struct SomeGroup<'a> {
504///     pub(crate) esi: &'a Esi,
505/// }
506///
507/// impl SomeGroup<'_> {
508///
509///     api_put!(
510///         /// Docs for the generated function
511///         function_name,
512///         "some_operation_id",
513///         RequestType::Authenticated,
514///         (),
515///         (fleet_id: i32) => "{fleet_id}",
516///         settings: &serde_json::Value,
517///     );
518///
519/// }
520/// # fn main() {}
521/// ```
522#[macro_export]
523macro_rules! api_put {
524    (
525        $(#[$m:meta])*
526        $fn_name:ident,
527        $op_id:literal,
528        $visibility:expr,
529        $ret_type:ty,
530        $( ($param:ident: $param_t:ty) => $replace:literal ),*,
531        $body_param:ident: $param_type:ty,
532    ) => {
533        $(#[$m])*
534        pub async fn $fn_name(&self, $( $param: $param_t, )* $body_param: $param_type) -> EsiResult<$ret_type> {
535            let path = self
536                .esi
537                .get_endpoint_for_op_id($op_id)?
538                $(
539                    .replace($replace, &$param.to_string())
540                )*;
541            let body = serde_json::to_string($body_param)?;
542            self.esi.
543                query("PUT", $visibility, &path, None, Some(&body))
544                .await
545        }
546    };
547    (
548        $(#[$m:meta])*
549        $fn_name:ident,
550        $op_id:literal,
551        $visibility:expr,
552        $ret_type:ty,
553        $( ($param:ident: $param_t:ty) => $replace:literal ),*
554        ; $( $qkind:ident($qparam:ident: $qparam_t:ty) => $qkey:literal ),*
555        ; $body_param:ident: $body_t:ty $(,)?
556    ) => {
557        $crate::__esi_endpoint!(
558            $(#[$m])*
559            $fn_name, $op_id, $visibility, $ret_type, "PUT",
560            [ $( ($param: $param_t) => $replace ),* ]
561            [ $( $qkind($qparam: $qparam_t) => $qkey ),* ]
562            [ $body_param: $body_t ]
563        );
564    };
565}
566
567/// Create a function for calling a single endpoint
568/// with a DELETE request.
569///
570/// Follows the structure of the `api_get!` macro: path parameters,
571/// then required and optional query parameters. DELETE requests
572/// have no body.
573///
574/// # Example
575///
576/// ```rust,no_run
577/// # use esi_openapi::prelude::*;
578/// # use esi_openapi::api_delete;
579/// pub struct SomeGroup<'a> {
580///     pub(crate) esi: &'a Esi,
581/// }
582///
583/// impl SomeGroup<'_> {
584///
585///     api_delete!(
586///         /// Docs for the generated function
587///         function_name,
588///         "some_operation_id",
589///         RequestType::Authenticated,
590///         (),
591///         (character_id: i64) => "{character_id}",
592///         (fitting_id: i32) => "{fitting_id}"
593///     );
594///
595/// }
596/// # fn main() {}
597/// ```
598#[macro_export]
599macro_rules! api_delete {
600    (
601        $(#[$m:meta])*
602        $fn_name:ident,
603        $op_id:literal,
604        $visibility:expr,
605        $ret_type:ty,
606        $( ($param:ident: $param_t:ty) => $replace:literal ),*
607    ) => {
608        $(#[$m])*
609        pub async fn $fn_name(&self, $( $param: $param_t, )*) -> EsiResult<$ret_type> {
610            let path = self
611                .esi
612                .get_endpoint_for_op_id($op_id)?
613                $(
614                    .replace($replace, &$param.to_string())
615                )*;
616            self.esi.
617                query("DELETE", $visibility, &path, None, None)
618                .await
619        }
620    };
621    (
622        $(#[$m:meta])*
623        $fn_name:ident,
624        $op_id:literal,
625        $visibility:expr,
626        $ret_type:ty,
627        $( ($param:ident: $param_t:ty) => $replace:literal ),*
628        $( ; $( ($qparam:ident: $qparam_t:ty) => $qreplace:literal ),+ )?
629    ) => {
630        $(#[$m])*
631        pub async fn $fn_name(
632            &self,
633            $( $param: $param_t, )*
634            $($( $qparam: $qparam_t, )*)?
635        ) -> EsiResult<$ret_type> {
636            let path = self
637                .esi
638                .get_endpoint_for_op_id($op_id)?
639                $(
640                    .replace($replace, &$param.to_string())
641                )*;
642            let params = vec![
643                $($(
644                    ($qreplace, $qparam.to_string()),
645                )+)?
646            ];
647            let params: Vec<(&str, &str)> = params.iter().map(|(a, b)| (*a, &**b)).collect();
648            self.esi.
649                query("DELETE", $visibility, &path, Some(&params), None)
650                .await
651        }
652    };
653    (
654        $(#[$m:meta])*
655        $fn_name:ident,
656        $op_id:literal,
657        $visibility:expr,
658        $ret_type:ty,
659        $( ($param:ident: $param_t:ty) => $replace:literal ),*
660        ; $( $qkind:ident($qparam:ident: $qparam_t:ty) => $qkey:literal ),+ $(,)?
661    ) => {
662        $crate::__esi_endpoint!(
663            $(#[$m])*
664            $fn_name, $op_id, $visibility, $ret_type, "DELETE",
665            [ $( ($param: $param_t) => $replace ),* ]
666            [ $( $qkind($qparam: $qparam_t) => $qkey ),+ ]
667            [ ]
668        );
669    };
670}