summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorD. Scott Boggs <scott@tams.tech>2022-12-23 10:09:33 -0500
committerD. Scott Boggs <scott@tams.tech>2022-12-23 12:18:03 -0500
commited497d96d46fb680de23413175ee92d139fdca66 (patch)
treed45d918070a16e2c1defa5add07a4344dde534f2
parent58ffee197018d5892dec65ef201273e1f39dd53e (diff)
Improve remote error handling
-rw-r--r--Cargo.toml3
-rw-r--r--README.md28
-rw-r--r--examples/follow_profile.rs13
-rw-r--r--examples/follows_me.rs24
-rw-r--r--examples/home_timeline.rs30
-rw-r--r--examples/log_events.rs1
-rw-r--r--examples/register/mod.rs4
-rw-r--r--examples/upload_photo.rs43
-rw-r--r--src/entities/attachment.rs2
-rw-r--r--src/errors.rs73
-rw-r--r--src/helpers/env.rs3
-rw-r--r--src/helpers/read_response.rs45
-rw-r--r--src/macros.rs166
-rw-r--r--src/mastodon.rs82
-rw-r--r--src/page.rs44
15 files changed, 291 insertions, 270 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 5f63941..b747842 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -70,10 +70,11 @@ features = ["io"]
tokio-test = "0.4.2"
futures-util = "0.3.25"
indoc = "1.0"
-pretty_env_logger = "0.3.0"
skeptic = "0.13"
tempfile = "3"
+# for examples:
femme = "2.2.1"
+html2text = "0.4.4"
[build-dependencies.skeptic]
version = "0.13"
diff --git a/README.md b/README.md
index a30586e..3377555 100644
--- a/README.md
+++ b/README.md
@@ -38,18 +38,16 @@ features = ["toml"]
```rust,no_run
// src/main.rs
-use std::error::Error;
-
use mastodon_async::prelude::*;
use mastodon_async::helpers::toml; // requires `features = ["toml"]`
-use mastodon_async::helpers::cli;
+use mastodon_async::{helpers::cli, Result};
#[tokio::main]
-async fn main() -> Result<(), Box<dyn Error>> {
+async fn main() -> Result<()> {
let mastodon = if let Ok(data) = toml::from_file("mastodon-data.toml") {
Mastodon::from(data)
} else {
- register()?
+ register().await?
};
let you = mastodon.verify_credentials().await?;
@@ -59,14 +57,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
Ok(())
}
-fn register() -> Result<Mastodon, Box<dyn Error>> {
+async fn register() -> Result<Mastodon> {
let registration = Registration::new("https://botsin.space")
.client_name("mastodon-async-examples")
- .build()?;
- let mastodon = cli::authenticate(registration)?;
+ .build()
+ .await?;
+ let mastodon = cli::authenticate(registration).await?;
// Save app data for using on the next run.
- toml::to_file(&*mastodon, "mastodon-data.toml")?;
+ toml::to_file(&mastodon.data, "mastodon-data.toml")?;
Ok(mastodon)
}
@@ -75,24 +74,23 @@ fn register() -> Result<Mastodon, Box<dyn Error>> {
It also supports the [Streaming API](https://docs.joinmastodon.org/api/streaming):
```rust,no_run
-use mastodon_async::prelude::*;
-use mastodon_async::entities::event::Event;
-
-use std::error::Error;
+use mastodon_async::{prelude::*, Result, entities::event::Event};
+use futures_util::TryStreamExt;
#[tokio::main]
-async fn main() -> Result<(), Box<Error>> {
+async fn main() -> Result<()> {
let client = Mastodon::from(Data::default());
client.stream_user()
.await?
- .try_for_each(|event| {
+ .try_for_each(|event| async move {
match event {
Event::Update(ref status) => { /* .. */ },
Event::Notification(ref notification) => { /* .. */ },
Event::Delete(ref id) => { /* .. */ },
Event::FiltersChanged => { /* .. */ },
}
+ Ok(())
})
.await?;
Ok(())
diff --git a/examples/follow_profile.rs b/examples/follow_profile.rs
index a5f7d4a..57e8f9d 100644
--- a/examples/follow_profile.rs
+++ b/examples/follow_profile.rs
@@ -1,17 +1,14 @@
#![cfg_attr(not(feature = "toml"), allow(dead_code))]
#![cfg_attr(not(feature = "toml"), allow(unused_imports))]
-#[macro_use]
-extern crate pretty_env_logger;
mod register;
-
-use register::Mastodon;
-use std::error;
+use mastodon_async::Result;
#[cfg(feature = "toml")]
-fn main() -> Result<(), Box<error::Error>> {
- let mastodon = register::get_mastodon_data()?;
+#[tokio::main]
+async fn main() -> Result<()> {
+ let mastodon = register::get_mastodon_data().await?;
let input = register::read_line("Enter the account id you'd like to follow: ")?;
- let new_follow = mastodon.follow(input.trim())?;
+ let new_follow = mastodon.follow(input.trim()).await?;
println!("{:#?}", new_follow);
Ok(())
diff --git a/examples/follows_me.rs b/examples/follows_me.rs
index c491994..be4b100 100644
--- a/examples/follows_me.rs
+++ b/examples/follows_me.rs
@@ -1,18 +1,22 @@
#![cfg_attr(not(feature = "toml"), allow(dead_code))]
#![cfg_attr(not(feature = "toml"), allow(unused_imports))]
-#[macro_use]
-extern crate pretty_env_logger;
mod register;
-
-use register::Mastodon;
-use std::error;
+use mastodon_async::Result;
#[cfg(feature = "toml")]
-fn main() -> Result<(), Box<error::Error>> {
- let mastodon = register::get_mastodon_data()?;
- for account in mastodon.follows_me()?.items_iter() {
- println!("{}", account.acct);
- }
+#[tokio::main]
+async fn main() -> Result<()> {
+ use futures::StreamExt;
+
+ let mastodon = register::get_mastodon_data().await?;
+ mastodon
+ .follows_me()
+ .await?
+ .items_iter()
+ .for_each(|account| async move {
+ println!("{}", account.acct);
+ })
+ .await;
Ok(())
}
diff --git a/examples/home_timeline.rs b/examples/home_timeline.rs
index ae279a9..c97bf48 100644
--- a/examples/home_timeline.rs
+++ b/examples/home_timeline.rs
@@ -1,19 +1,27 @@
#![cfg_attr(not(feature = "toml"), allow(dead_code))]
#![cfg_attr(not(feature = "toml"), allow(unused_imports))]
-#[macro_use]
-extern crate pretty_env_logger;
mod register;
-
-use register::Mastodon;
-use std::error;
+use futures_util::StreamExt;
+use mastodon_async::Result;
#[cfg(feature = "toml")]
-fn main() -> Result<(), Box<error::Error>> {
- let mastodon = register::get_mastodon_data()?;
- let tl = mastodon.get_home_timeline()?;
-
- println!("{:#?}", tl);
-
+#[tokio::main]
+async fn main() -> Result<()> {
+ register::get_mastodon_data()
+ .await?
+ .get_home_timeline()
+ .await?
+ .items_iter()
+ .for_each(|status| async move {
+ print!(
+ "\ttoot from {}:\n{}",
+ status.account.display_name,
+ html2text::parse(status.content.as_bytes())
+ .render_plain(90)
+ .into_string()
+ )
+ })
+ .await;
Ok(())
}
diff --git a/examples/log_events.rs b/examples/log_events.rs
index 9f93fbb..48d3b32 100644
--- a/examples/log_events.rs
+++ b/examples/log_events.rs
@@ -10,7 +10,6 @@ use mastodon_async::Result;
#[tokio::main]
async fn main() -> Result<()> {
use log::warn;
- use mastodon_async::entities::prelude::Event;
femme::with_level(log::LevelFilter::Info);
let mastodon = register::get_mastodon_data().await?;
diff --git a/examples/register/mod.rs b/examples/register/mod.rs
index eefa2fd..8daedb1 100644
--- a/examples/register/mod.rs
+++ b/examples/register/mod.rs
@@ -45,8 +45,8 @@ pub async fn register() -> Result<Mastodon> {
}
#[cfg(feature = "toml")]
-pub fn read_line(message: &str) -> Result<String> {
- println!("{}", message);
+pub fn read_line(message: impl AsRef<str>) -> Result<String> {
+ println!("{}", message.as_ref());
let mut input = String::new();
io::stdin().read_line(&mut input)?;
diff --git a/examples/upload_photo.rs b/examples/upload_photo.rs
index 52c5e09..b7806ef 100644
--- a/examples/upload_photo.rs
+++ b/examples/upload_photo.rs
@@ -1,15 +1,48 @@
#![cfg_attr(not(feature = "toml"), allow(dead_code))]
#![cfg_attr(not(feature = "toml"), allow(unused_imports))]
-#[macro_use]
-extern crate pretty_env_logger;
mod register;
+use mastodon_async::{Result, StatusBuilder, Visibility};
#[cfg(feature = "toml")]
-fn main() -> Result<(), Box<error::Error>> {
- let mastodon = register::get_mastodon_data()?;
+fn bool_input(message: impl AsRef<str>, default: bool) -> Result<bool> {
+ let input = register::read_line(message.as_ref())?;
+ if let Some(first_char) = input.chars().next() {
+ match first_char {
+ 'Y' | 'y' => Ok(true),
+ 'N' | 'n' => Ok(false),
+ '\n' => Ok(default),
+ _ => {
+ print!(
+ "I didn't understand '{input}'. Please input something that begins with 'y' \
+ or 'n', case insensitive: "
+ );
+ bool_input(message, default)
+ },
+ }
+ } else {
+ Ok(default)
+ }
+}
+
+#[cfg(feature = "toml")]
+#[tokio::main]
+async fn main() -> Result<()> {
+ femme::with_level(femme::LevelFilter::Trace);
+ let mastodon = register::get_mastodon_data().await?;
let input = register::read_line("Enter the path to the photo you'd like to post: ")?;
- mastodon.media(input.into())?;
+ let media = mastodon.media(input).await?;
+ let status = StatusBuilder::new()
+ .status("Mastodon-async photo upload example/demo (automated post)")
+ .media_ids([media.id])
+ .visibility(Visibility::Private)
+ .build()?;
+ let status = mastodon.new_status(status).await?;
+ println!("successfully uploaded status. It has the ID {}.", status.id);
+ if bool_input("would you like to delete the post now? (Y/n) ", true)? {
+ mastodon.delete_status(&status.id).await?;
+ println!("ok. done.");
+ }
Ok(())
}
diff --git a/src/entities/attachment.rs b/src/entities/attachment.rs
index 3a93440..2115b9f 100644
--- a/src/entities/attachment.rs
+++ b/src/entities/attachment.rs
@@ -11,7 +11,7 @@ pub struct Attachment {
#[serde(rename = "type")]
pub media_type: MediaType,
/// URL of the locally hosted version of the image.
- pub url: String,
+ pub url: Option<String>,
/// For remote images, the remote URL of the original image.
pub remote_url: Option<String>,
/// URL of the preview image.
diff --git a/src/errors.rs b/src/errors.rs
index cbe85a0..f04e545 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -22,7 +22,12 @@ pub type Result<T> = ::std::result::Result<T, Error>;
pub enum Error {
/// Error from the Mastodon API. This typically means something went
/// wrong with your authentication or data.
- Api(ApiError),
+ Api {
+ /// The response status.
+ status: StatusCode,
+ /// The JSON-decoded error response from the server.
+ response: ApiError,
+ },
/// Error deserialising to json. Typically represents a breaking change in
/// the Mastodon API
Serde(SerdeError),
@@ -40,10 +45,6 @@ pub enum Error {
ClientSecretRequired,
/// Missing Access Token.
AccessTokenRequired,
- /// Generic client error.
- Client(StatusCode),
- /// Generic server error.
- Server(StatusCode),
/// MastodonBuilder & AppBuilder error
MissingField(&'static str),
#[cfg(feature = "toml")]
@@ -79,39 +80,40 @@ impl fmt::Display for Error {
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
- Some(match *self {
- Error::Api(ref e) => e,
- Error::Serde(ref e) => e,
- Error::UrlEncoded(ref e) => e,
- Error::Http(ref e) => e,
- Error::Io(ref e) => e,
- Error::Url(ref e) => e,
+ match *self {
+ Error::Serde(ref e) => Some(e),
+ Error::UrlEncoded(ref e) => Some(e),
+ Error::Http(ref e) => Some(e),
+ Error::Io(ref e) => Some(e),
+ Error::Url(ref e) => Some(e),
#[cfg(feature = "toml")]
- Error::TomlSer(ref e) => e,
+ Error::TomlSer(ref e) => Some(e),
#[cfg(feature = "toml")]
- Error::TomlDe(ref e) => e,
- Error::HeaderStrError(ref e) => e,
- Error::HeaderParseError(ref e) => e,
+ Error::TomlDe(ref e) => Some(e),
+ Error::HeaderStrError(ref e) => Some(e),
+ Error::HeaderParseError(ref e) => Some(e),
#[cfg(feature = "env")]
- Error::Envy(ref e) => e,
- Error::SerdeQs(ref e) => e,
- Error::IntConversion(ref e) => e,
- Error::Client(..) | Error::Server(..) => return None,
- Error::ClientIdRequired => return None,
- Error::ClientSecretRequired => return None,
- Error::AccessTokenRequired => return None,
- Error::MissingField(_) => return None,
- Error::Other(..) => return None,
- })
+ Error::Envy(ref e) => Some(e),
+ Error::SerdeQs(ref e) => Some(e),
+ Error::IntConversion(ref e) => Some(e),
+ Error::Api {
+ ..
+ }
+ | Error::ClientIdRequired
+ | Error::ClientSecretRequired
+ | Error::AccessTokenRequired
+ | Error::MissingField(_)
+ | Error::Other(..) => None,
+ }
}
}
/// Error returned from the Mastodon API.
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ApiError {
- /// The type of error.
- pub error: Option<String>,
- /// The description of the error.
+ /// The error message.
+ pub error: String,
+ /// A longer description of the error, mainly provided with the OAuth API.
pub error_description: Option<String>,
}
@@ -143,7 +145,6 @@ from! {
SerdeError => Serde,
UrlEncodedError => UrlEncoded,
UrlError => Url,
- ApiError => Api,
#[cfg(feature = "toml")] TomlSerError => TomlSer,
#[cfg(feature = "toml")] TomlDeError => TomlDe,
HeaderStrError => HeaderStrError,
@@ -213,16 +214,6 @@ mod tests {
assert_is!(err, Error::Url(..));
}
- #[test]
- fn from_api_error() {
- let err: ApiError = ApiError {
- error: None,
- error_description: None,
- };
- let err: Error = Error::from(err);
- assert_is!(err, Error::Api(..));
- }
-
#[cfg(feature = "toml")]
#[test]
fn from_toml_ser_error() {
diff --git a/src/helpers/env.rs b/src/helpers/env.rs
index 00a1487..df44028 100644
--- a/src/helpers/env.rs
+++ b/src/helpers/env.rs
@@ -1,7 +1,6 @@
use envy;
-use crate::Result;
-use data::Data;
+use crate::{Data, Result};
/// Attempts to deserialize a Data struct from the environment
pub fn from_env() -> Result<Data> {
diff --git a/src/helpers/read_response.rs b/src/helpers/read_response.rs
index 66e6be8..0d876dc 100644
--- a/src/helpers/read_response.rs
+++ b/src/helpers/read_response.rs
@@ -1,23 +1,27 @@
use std::time::Duration;
-use crate::errors::Result;
+use crate::{errors::Result, log_serde, Error};
use futures::pin_mut;
use futures_util::StreamExt;
-use log::{as_serde, debug, trace, warn};
+use log::{as_debug, as_serde, debug, trace, warn};
use reqwest::Response;
use serde::{Deserialize, Serialize};
use tokio::time::timeout;
/// Adapter for reading JSON data from a response with better logging and a
/// fail-safe timeout.
+///
+/// The reason for this is largely because there was an issue with responses
+/// being received, but not closed, we add a timeout on each read and try
+/// to parse whatever we got before the timeout.
pub async fn read_response<T>(response: Response) -> Result<T>
where
T: for<'de> Deserialize<'de> + Serialize,
{
let mut bytes = vec![];
let url = response.url().clone();
- // let status = log_serde!(response Status);
- // let headers = log_serde!(response Headers);
+ let status = response.status();
+ trace!(status = log_serde!(response Status), headers = log_serde!(response Headers); "attempting to stream response");
let stream = response.bytes_stream();
pin_mut!(stream);
loop {
@@ -35,23 +39,36 @@ where
);
} else {
warn!(
- url = url.as_str(), // status = status, headers = headers,
+ url = url.as_str(),
data_received = bytes.len();
"API response timed out"
);
break;
}
}
+ // done growing the vec, let's just do this once.
+ let bytes = bytes.as_slice();
trace!(
- url = url.as_str(), // status = status, headers = headers,
- data_received = bytes.len();
+ url = url.as_str(),
+ data = String::from_utf8_lossy(bytes);
"parsing response"
);
- let result = serde_json::from_slice(bytes.as_slice())?;
- debug!(
- url = url.as_str(), // status = status, headers = headers,
- result = as_serde!(result);
- "result parsed successfully"
- );
- Ok(result)
+ if status.is_success() {
+ // the the response should deserialize to T
+ let result = serde_json::from_slice(bytes)?;
+ debug!(
+ url = url.as_str(),
+ result = as_serde!(result);
+ "result parsed successfully"
+ );
+ Ok(result)
+ } else {
+ // we've received an error message, let's deserialize that instead.
+ let response = serde_json::from_slice(bytes)?;
+ debug!(status = as_debug!(status), response = as_serde!(response); "error received from API");
+ Err(Error::Api {
+ status,
+ response,
+ })
+ }
}
diff --git a/src/macros.rs b/src/macros.rs
index cd48c2f..ce0c436 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -18,22 +18,12 @@ macro_rules! methods {
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};
+ use log::{debug, as_debug};
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?;
- match response.error_for_status() {
- Ok(response) => {
- let response = read_response(response).await?;
- 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())
- }
- }
+ let response = self.authenticated(self.client.$method(url)).header("Accept", "application/json").send().await?;
+ read_response(response).await
}
}
)+
@@ -57,21 +47,13 @@ macro_rules! paged_routes {
"```"
),
pub async fn $name(&self) -> Result<Page<$ret>> {
- use log::{debug, as_debug, error};
+ use log::{debug, as_debug};
let url = self.route(concat!("/api/v1/", $url));
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?;
-
- 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())
- }
- }
+ let response = self.authenticated(self.client.$method(&url)).header("Accept", "application/json").send().await?;
+
+ Page::new(self.clone(), response, call_id).await
}
}
@@ -88,7 +70,7 @@ macro_rules! paged_routes {
),
pub async fn $name<'a>(&self, $($param: $typ,)*) -> Result<Page<$ret>> {
use serde_urlencoded;
- use log::{debug, as_debug, error};
+ use log::{debug, as_debug};
let call_id = uuid::Uuid::new_v4();
@@ -117,17 +99,9 @@ macro_rules! paged_routes {
debug!(url = url, method = "get", call_id = as_debug!(call_id); "making API request");
- let response = self.authenticated(self.client.get(&url)).send().await?;
+ let response = self.authenticated(self.client.get(&url)).header("Accept", "application/json").send().await?;
- 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())
- }
- }
+ Page::new(self.clone(), response, call_id).await
}
}
@@ -181,6 +155,62 @@ macro_rules! route_v2 {
route_v2!{$($rest)*}
};
+ ((post multipart ($($param:ident: $typ:ty,)*)) $name:ident: $url:expr => $ret:ty, $($rest:tt)*) => {
+ doc_comment! {
+ concat!(
+ "Equivalent to `post /api/v2/",
+ $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;
+ use log::{debug, error, as_debug};
+ use uuid::Uuid;
+
+ let call_id = Uuid::new_v4();
+
+ let form_data = Form::new()
+ $(
+ .part(stringify!($param), {
+ let path = $param.as_ref();
+ match std::fs::File::open(path) {
+ 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");
+ return Err(err.into());
+ }
+ }
+ })
+ )*;
+
+ let url = &self.route(concat!("/api/v2/", $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)
+ .header("Accept", "application/json")
+ .send()
+ .await?;
+
+ read_response(response).await
+ }
+ }
+
+ route!{$($rest)*}
+ };
() => {}
}
@@ -195,7 +225,7 @@ macro_rules! route {
pub async fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
use reqwest::multipart::{Form, Part};
use std::io::Read;
- use log::{debug, error, as_debug, as_serde};
+ use log::{debug, error, as_debug};
use uuid::Uuid;
let call_id = Uuid::new_v4();
@@ -232,20 +262,11 @@ macro_rules! route {
let response = self.authenticated(self.client.post(url))
.multipart(form_data)
+ .header("Accept", "application/json")
.send()
.await?;
- match response.error_for_status() {
- Ok(response) => {
- let response = read_response(response).await?;
- 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())
- }
- }
+ read_response(response).await
}
}
@@ -304,7 +325,7 @@ macro_rules! route {
"`\n# Errors\nIf `access_token` is not set.",
),
pub async fn $name(&self, $($param: $typ,)*) -> Result<$ret> {
- use log::{debug, error, as_debug, as_serde};
+ use log::{debug, as_debug, as_serde};
use uuid::Uuid;
let call_id = Uuid::new_v4();
@@ -324,20 +345,11 @@ macro_rules! route {
let response = self.authenticated(self.client.$method(url))
.json(&form_data)
+ .header("Accept", "application/json")
.send()
.await?;
- match response.error_for_status() {
- Ok(response) => {
- let response = read_response(response).await?;
- 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())
- }
- }
+ read_response(response).await
}
}
@@ -413,23 +425,15 @@ macro_rules! paged_routes_with_id {
"```"
),
pub async fn $name(&self, id: &str) -> Result<Page<$ret>> {
- use log::{debug, error, as_debug};
+ use log::{debug, as_debug};
use uuid::Uuid;
let call_id = Uuid::new_v4();
let url = self.route(&format!(concat!("/api/v1/", $url), id));
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?;
- 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())
- }
- }
+ let response = self.authenticated(self.client.$method(&url)).header("Accept", "application/json").send().await?;
+ Page::new(self.clone(), response, call_id).await
}
}
@@ -469,13 +473,19 @@ tokio_test::block_on(async {
),
pub async fn $fn_name(&self) -> Result<impl TryStream<Ok=Event, Error=Error>> {
let url = self.route(&format!("/api/v1/streaming/{}", $stream));
- let response = self.authenticated(self.client.get(&url)).send().await?;
+ let response = self.authenticated(self.client.get(&url)).header("Accept", "application/json").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))
+ let status = response.status();
+ if status.is_success() {
+ Ok(event_stream(response, url))
+ } else {
+ let response = response.json().await?;
+ Err(Error::Api{ status, response })
+ }
}
}
streaming! { $($rest)* }
@@ -513,13 +523,19 @@ tokio_test::block_on(async {
let mut url: Url = self.route(concat!("/api/v1/streaming/", stringify!($stream))).parse()?;