mastodon-async/src/macros.rs

529 lines
19 KiB
Rust
Raw Normal View History

2022-11-27 14:44:43 +00:00
macro_rules! methods {
2022-12-07 20:58:28 +00:00
($($method:ident and $method_with_call_id:ident,)+) => {
2022-11-27 14:44:43 +00:00
$(
2022-12-07 20:58:28 +00:00
doc_comment! {
concat!("Make a ", stringify!($method), " API request, and deserialize the result into T"),
async fn $method<T: for<'de> serde::Deserialize<'de> + serde::Serialize>(&self, url: impl AsRef<str>) -> Result<T>
{
let call_id = uuid::Uuid::new_v4();
self.$method_with_call_id(url, call_id).await
}
}
doc_comment! {
concat!(
"Make a ", stringify!($method), " API request, and deserialize the result into T.\n\n",
"Logging will use the provided UUID, rather than generating one before making the request.",
),
async fn $method_with_call_id<T: for<'de> serde::Deserialize<'de> + serde::Serialize>(&self, url: impl AsRef<str>, call_id: Uuid) -> Result<T>
{
use log::{debug, error, as_debug, as_serde};
let url = url.as_ref();
debug!(url = url, method = stringify!($method), call_id = as_debug!(call_id); "making API request");
let response = self.authenticated(self.client.$method(url)).send().await?;
2022-12-07 20:58:28 +00:00
match response.error_for_status() {
Ok(response) => {
let response = read_response(response).await?;
2022-12-07 20:58:28 +00:00
debug!(response = as_serde!(response), url = url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
Ok(response)
}
Err(err) => {
error!(err = as_debug!(err), url = url, method = stringify!($method), call_id = as_debug!(call_id); "error making API request");
Err(err.into())
}
}
}
2022-11-27 14:44:43 +00:00
}
)+
};
}
macro_rules! paged_routes {
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `", stringify!($method), " /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set.",
"\n",
"```no_run",
"use elefren::prelude::*;\n",
"let data = Data::default();\n",
2022-11-27 14:44:43 +00:00
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "();\n",
"```"
),
pub async fn $name(&self) -> Result<Page<$ret>> {
2022-12-07 20:58:28 +00:00
use log::{debug, as_debug, error};
2022-11-27 14:44:43 +00:00
let url = self.route(concat!("/api/v1/", $url));
2022-12-07 20:58:28 +00:00
let call_id = uuid::Uuid::new_v4();
debug!(url = url, method = stringify!($method), call_id = as_debug!(call_id); "making API request");
let response = self.authenticated(self.client.$method(&url)).send().await?;
2022-11-27 14:44:43 +00:00
2022-12-07 20:58:28 +00:00
match response.error_for_status() {
Ok(response) => {
Page::new(self.clone(), response, call_id).await
}
Err(err) => {
error!(err = as_debug!(err), url = url, method = stringify!($method), call_id = as_debug!(call_id); "error making API request");
Err(err.into())
}
}
2022-11-27 14:44:43 +00:00
}
}
paged_routes!{$($rest)*}
};
((get ($($(#[$m:meta])* $param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `get /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set."
),
pub async fn $name<'a>(&self, $($param: $typ,)*) -> Result<Page<$ret>> {
2022-11-27 14:44:43 +00:00
use serde_urlencoded;
2022-12-07 20:58:28 +00:00
use log::{debug, as_debug, error};
let call_id = uuid::Uuid::new_v4();
2022-11-27 14:44:43 +00:00
#[derive(Serialize)]
struct Data<'a> {
$(
$(
#[$m]
)*
$param: $typ,
)*
#[serde(skip)]
_marker: ::std::marker::PhantomData<&'a ()>,
}
let qs_data = Data {
$(
$param: $param,
)*
_marker: ::std::marker::PhantomData,
};
let qs = serde_urlencoded::to_string(&qs_data)?;
let url = format!(concat!("/api/v1/", $url, "?{}"), &qs);
2022-12-07 20:58:28 +00:00
debug!(url = url, method = "get", call_id = as_debug!(call_id); "making API request");
let response = self.authenticated(self.client.get(&url)).send().await?;
2022-11-27 14:44:43 +00:00
2022-12-07 20:58:28 +00:00
match response.error_for_status() {
Ok(response) => {
Page::new(self.clone(), response, call_id).await
}
Err(err) => {
error!(err = as_debug!(err), url = url, method = stringify!($method), call_id = as_debug!(call_id); "error making API request");
Err(err.into())
}
}
2022-11-27 14:44:43 +00:00
}
}
paged_routes!{$($rest)*}
};
() => {}
}
macro_rules! route_v2 {
((get ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `get /api/v2/",
$url,
"`\n# Errors\nIf `access_token` is not set."
),
pub async fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
2022-11-27 14:44:43 +00:00
use serde_urlencoded;
2022-12-07 20:58:28 +00:00
use log::{debug, as_serde};
use uuid::Uuid;
let call_id = Uuid::new_v4();
2022-11-27 14:44:43 +00:00
#[derive(Serialize)]
struct Data<'a> {
$(
$param: $typ,
)*
#[serde(skip)]
_marker: ::std::marker::PhantomData<&'a ()>,
}
let qs_data = Data {
$(
$param: $param,
)*
_marker: ::std::marker::PhantomData,
};
let qs = serde_urlencoded::to_string(&qs_data)?;
2022-12-07 20:58:28 +00:00
debug!(query_string_data = as_serde!(qs_data); "URL-encoded data to be sent in API request");
2022-11-27 14:44:43 +00:00
let url = format!(concat!("/api/v2/", $url, "?{}"), &qs);
2022-12-07 20:58:28 +00:00
self.get_with_call_id(self.route(&url), call_id).await
2022-11-27 14:44:43 +00:00
}
}
route_v2!{$($rest)*}
};
() => {}
}
macro_rules! route {
((post multipart ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `post /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set."),
pub async fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
use reqwest::multipart::{Form, Part};
use std::io::Read;
2022-12-07 20:58:28 +00:00
use log::{debug, error, as_debug, as_serde};
use uuid::Uuid;
let call_id = Uuid::new_v4();
2022-11-27 14:44:43 +00:00
let form_data = Form::new()
$(
.part(stringify!($param), {
let path = $param.as_ref();
match std::fs::File::open(path) {
2022-12-07 20:58:28 +00:00
Ok(mut file) => {
let mut data = if let Ok(metadata) = file.metadata() {
Vec::with_capacity(metadata.len().try_into()?)
} else {
vec![]
};
file.read_to_end(&mut data)?;
Part::bytes(data)
}
Err(err) => {
error!(path = as_debug!(path), error = as_debug!(err); "error reading file contents for multipart form");
2022-12-07 20:58:28 +00:00
return Err(err.into());
}
}
})
2022-11-27 14:44:43 +00:00
)*;
2022-12-07 20:58:28 +00:00
let url = &self.route(concat!("/api/v1/", $url));
debug!(
url = url, method = stringify!($method),
multipart_form_data = as_debug!(form_data), call_id = as_debug!(call_id);
"making API request"
);
let response = self.authenticated(self.client.post(url))
.multipart(form_data)
.send()
.await?;
2022-11-27 14:44:43 +00:00
2022-12-07 20:58:28 +00:00
match response.error_for_status() {
Ok(response) => {
let response = read_response(response).await?;
2022-12-07 20:58:28 +00:00
debug!(response = as_serde!(response), url = url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
Ok(response)
}
Err(err) => {
error!(err = as_debug!(err), url = url, method = stringify!($method), call_id = as_debug!(call_id); "error making API request");
Err(err.into())
}
2022-11-27 14:44:43 +00:00
}
}
}
route!{$($rest)*}
};
((get ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `get /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set."
),
pub async fn $name<'a>(&self, $($param: $typ,)*) -> Result<$ret> {
2022-11-27 14:44:43 +00:00
use serde_urlencoded;
2022-12-07 20:58:28 +00:00
use log::{debug, as_serde};
use uuid::Uuid;
let call_id = Uuid::new_v4();
2022-11-27 14:44:43 +00:00
#[derive(Serialize)]
struct Data<'a> {
$(
$param: $typ,
)*
#[serde(skip)]
_marker: ::std::marker::PhantomData<&'a ()>,
}
let qs_data = Data {
$(
$param: $param,
)*
_marker: ::std::marker::PhantomData,
};
2022-12-07 20:58:28 +00:00
2022-11-27 14:44:43 +00:00
let qs = serde_urlencoded::to_string(&qs_data)?;
2022-12-07 20:58:28 +00:00
debug!(query_string_data = as_serde!(qs_data); "URL-encoded data to be sent in API request");
2022-11-27 14:44:43 +00:00
let url = format!(concat!("/api/v1/", $url, "?{}"), &qs);
2022-12-07 20:58:28 +00:00
self.get_with_call_id(self.route(&url), call_id).await
2022-11-27 14:44:43 +00:00
}
}
route!{$($rest)*}
};
(($method:ident ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `", stringify!($method), " /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set.",
),
pub async fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
2022-12-07 20:58:28 +00:00
use log::{debug, error, as_debug, as_serde};
use uuid::Uuid;
let call_id = Uuid::new_v4();
2022-11-27 14:44:43 +00:00
let form_data = json!({
$(
stringify!($param): $param,
)*
});
let url = &self.route(concat!("/api/v1/", $url));
2022-12-07 20:58:28 +00:00
debug!(
url = url.as_str(), method = stringify!($method),
2022-12-07 20:58:28 +00:00
call_id = as_debug!(call_id),
form_data = as_serde!(&form_data);
"making API request"
);
2022-11-27 14:44:43 +00:00
let response = self.authenticated(self.client.$method(url))
.json(&form_data)
.send()
.await?;
2022-11-27 14:44:43 +00:00
2022-12-07 20:58:28 +00:00
match response.error_for_status() {
Ok(response) => {
let response = read_response(response).await?;
2022-12-07 20:58:28 +00:00
debug!(response = as_serde!(response), url = $url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
Ok(response)
}
Err(err) => {
error!(err = as_debug!(err), url = $url, method = stringify!($method), call_id = as_debug!(call_id); "error making API request");
Err(err.into())
}
2022-11-27 14:44:43 +00:00
}
}
}
route!{$($rest)*}
};
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `", stringify!($method), " /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set.",
"\n",
"```no_run",
"use elefren::prelude::*;\n",
"let data = Data::default();\n",
2022-11-27 14:44:43 +00:00
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "();\n",
"```"
),
pub async fn $name(&self) -> Result<$ret> {
self.$method(self.route(concat!("/api/v1/", $url))).await
2022-11-27 14:44:43 +00:00
}
}
route!{$($rest)*}
};
() => {}
}
macro_rules! route_id {
($(($method:ident) $name:ident: $url:expr => $ret:ty,)*) => {
$(
doc_comment! {
concat!(
"Equivalent to `", stringify!($method), " /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set.",
"\n",
"```no_run",
"use elefren::prelude::*;\n",
"let data = Data::default();\n",
2022-11-27 14:44:43 +00:00
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "(\"42\");\n",
"# Ok(())\n",
"# }\n",
"```"
),
pub async fn $name(&self, id: &str) -> Result<$ret> {
self.$method(self.route(&format!(concat!("/api/v1/", $url), id))).await
2022-11-27 14:44:43 +00:00
}
}
)*
}
}
macro_rules! paged_routes_with_id {
(($method:ident) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
doc_comment! {
concat!(
"Equivalent to `", stringify!($method), " /api/v1/",
$url,
"`\n# Errors\nIf `access_token` is not set.",
"\n",
"```no_run",
"use elefren::prelude::*;\n",
"let data = Data::default();",
2022-11-27 14:44:43 +00:00
"let client = Mastodon::from(data);\n",
"client.", stringify!($name), "(\"some-id\");\n",
"```"
),
pub async fn $name(&self, id: &str) -> Result<Page<$ret>> {
2022-12-07 20:58:28 +00:00
use log::{debug, error, as_debug};
use uuid::Uuid;
let call_id = Uuid::new_v4();
2022-11-27 14:44:43 +00:00
let url = self.route(&format!(concat!("/api/v1/", $url), id));
2022-12-07 20:58:28 +00:00
debug!(url = url, method = stringify!($method), call_id = as_debug!(call_id); "making API request");
let response = self.authenticated(self.client.$method(&url)).send().await?;
2022-12-07 20:58:28 +00:00
match response.error_for_status() {
Ok(response) => {
Page::new(self.clone(), response, call_id).await
}
Err(err) => {
error!(err = as_debug!(err), url = url, method = stringify!($method), call_id = as_debug!(call_id); "error making API request");
Err(err.into())
}
}
2022-11-27 14:44:43 +00:00
}
}
paged_routes_with_id!{$($rest)*}
};
() => {}
}
2022-12-18 22:30:23 +00:00
macro_rules! streaming {
2022-12-22 17:28:08 +00:00
($desc:tt $fn_name:ident@$stream:literal, $($rest:tt)*) => {
2022-12-18 23:00:58 +00:00
doc_comment! {
concat!(
$desc,
"\n\nExample:\n\n",
"
2022-12-18 22:30:23 +00:00
use elefren::prelude::*;
use elefren::entities::event::Event;
use futures_util::{pin_mut, StreamExt, TryStreamExt};
tokio_test::block_on(async {
let data = Data::default();
let client = Mastodon::from(data);
let stream = client.",
stringify!($fn_name),
"().await.unwrap();
stream.try_for_each(|event| async move {
match event {
Event::Update(ref status) => { /* .. */ },
Event::Notification(ref notification) => { /* .. */ },
Event::Delete(ref id) => { /* .. */ },
Event::FiltersChanged => { /* .. */ },
}
Ok(())
}).await.unwrap();
});"
2022-12-18 23:00:58 +00:00
),
pub async fn $fn_name(&self) -> Result<impl TryStream<Ok=Event, Error=Error>> {
2022-12-22 17:28:08 +00:00
let url = self.route(&format!("/api/v1/streaming/{}", $stream));
2022-12-18 23:00:58 +00:00
let response = self.authenticated(self.client.get(&url)).send().await?;
debug!(
status = log_serde!(response Status), url = &url,
headers = log_serde!(response Headers);
"received API response"
);
Ok(event_stream(response.error_for_status()?, url))
}
}
streaming! { $($rest)* }
};
2022-12-22 17:28:08 +00:00
($desc:tt $fn_name:ident($param:ident: $param_type:ty, like $param_doc_val:literal)@$stream:literal, $($rest:tt)*) => {
2022-12-18 23:00:58 +00:00
doc_comment! {
concat!(
$desc,
"\n\nExample:\n\n",
"
use elefren::prelude::*;
use elefren::entities::event::Event;
use futures_util::{pin_mut, StreamExt, TryStreamExt};
tokio_test::block_on(async {
let data = Data::default();
let client = Mastodon::from(data);
let stream = client.",
stringify!($fn_name),
"(",
$param_doc_val,
").await.unwrap();
stream.try_for_each(|event| async move {
match event {
Event::Update(ref status) => { /* .. */ },
Event::Notification(ref notification) => { /* .. */ },
Event::Delete(ref id) => { /* .. */ },
Event::FiltersChanged => { /* .. */ },
}
Ok(())
}).await.unwrap();
});"
),
pub async fn $fn_name(&self, $param: $param_type) -> Result<impl TryStream<Ok=Event, Error=Error>> {
let mut url: Url = self.route(concat!("/api/v1/streaming/", stringify!($stream))).parse()?;
url.query_pairs_mut().append_pair(stringify!($param), $param.as_ref());
let url = url.to_string();
let response = self.authenticated(self.client.get(url.as_str())).send().await?;
debug!(
status = log_serde!(response Status), url = as_debug!(url),
headers = log_serde!(response Headers);
"received API response"
);
Ok(event_stream(response.error_for_status()?, url))
2022-12-18 22:30:23 +00:00
}
2022-12-18 23:00:58 +00:00
}
streaming! { $($rest)* }
2022-12-18 22:30:23 +00:00
};
2022-12-18 23:00:58 +00:00
() => {}
2022-12-18 22:30:23 +00:00
}