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}", ®ion_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(¶ms), 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(¶ms), 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(¶ms), 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(¶ms), 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}