diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/apps.rs | 10 | ||||
-rw-r--r-- | src/entities/attachment.rs | 2 | ||||
-rw-r--r-- | src/entities/itemsiter.rs | 2 | ||||
-rw-r--r-- | src/entities/mod.rs | 2 | ||||
-rw-r--r-- | src/entities/search_result.rs | 13 | ||||
-rw-r--r-- | src/entities/status.rs | 15 | ||||
-rw-r--r-- | src/errors.rs | 73 | ||||
-rw-r--r-- | src/helpers/env.rs | 3 | ||||
-rw-r--r-- | src/helpers/log.rs | 2 | ||||
-rw-r--r-- | src/helpers/mod.rs | 4 | ||||
-rw-r--r-- | src/helpers/read_response.rs | 45 | ||||
-rw-r--r-- | src/lib.rs | 10 | ||||
-rw-r--r-- | src/macros.rs | 182 | ||||
-rw-r--r-- | src/mastodon.rs | 89 | ||||
-rw-r--r-- | src/page.rs | 48 | ||||
-rw-r--r-- | src/registration.rs | 14 | ||||
-rw-r--r-- | src/requests/filter.rs | 2 | ||||
-rw-r--r-- | src/requests/push.rs | 30 | ||||
-rw-r--r-- | src/requests/statuses.rs | 18 | ||||
-rw-r--r-- | src/requests/update_credentials.rs | 26 | ||||
-rw-r--r-- | src/scopes.rs | 18 | ||||
-rw-r--r-- | src/status_builder.rs | 22 |
22 files changed, 303 insertions, 327 deletions
diff --git a/src/apps.rs b/src/apps.rs index fef26fc..b85321a 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -24,7 +24,7 @@ impl App { /// // Example /// /// ``` - /// use elefren::apps::App; + /// use mastodon_async::apps::App; /// /// let mut builder = App::builder(); /// ``` @@ -37,10 +37,10 @@ impl App { /// // Example /// /// ``` - /// use elefren::{apps::App, scopes::Scopes}; + /// use mastodon_async::{apps::App, scopes::Scopes}; /// /// let mut builder = App::builder(); - /// builder.client_name("elefren-test"); + /// builder.client_name("mastodon-async-test"); /// let app = builder.build().unwrap(); /// let scopes = app.scopes(); /// assert_eq!(scopes, &Scopes::read_all()); @@ -52,10 +52,10 @@ impl App { /// Builder struct for defining your application. /// ``` -/// use elefren::{apps::App}; +/// use mastodon_async::{apps::App}; /// /// let mut builder = App::builder(); -/// builder.client_name("elefren_test"); +/// builder.client_name("mastodon-async_test"); /// let app = builder.build().unwrap(); /// ``` #[derive(Clone, Debug, Default, PartialEq, Serialize)] 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/entities/itemsiter.rs b/src/entities/itemsiter.rs index ef97334..d8df189 100644 --- a/src/entities/itemsiter.rs +++ b/src/entities/itemsiter.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; /// Abstracts away the `next_page` logic into a single stream of items /// /// ```no_run,async -/// use elefren::prelude::*; +/// use mastodon_async::prelude::*; /// use futures::stream::StreamExt; /// use futures_util::pin_mut; /// diff --git a/src/entities/mod.rs b/src/entities/mod.rs index aef8121..f726abc 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -54,7 +54,7 @@ pub mod prelude { push::Subscription, relationship::Relationship, report::Report, - search_result::{SearchResult, SearchResultV2}, + search_result::SearchResult, status::{Application, Emoji, Status}, Empty, }; diff --git a/src/entities/search_result.rs b/src/entities/search_result.rs index 499af5f..e757f71 100644 --- a/src/entities/search_result.rs +++ b/src/entities/search_result.rs @@ -7,21 +7,10 @@ use super::{ status::Tag, }; -/// A struct containing results of a search. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct SearchResult { - /// An array of matched Accounts. - pub accounts: Vec<Account>, - /// An array of matched Statuses. - pub statuses: Vec<Status>, - /// An array of matched hashtags, as strings. - pub hashtags: Vec<String>, -} - /// A struct containing results of a search, with `Tag` objects in the /// `hashtags` field #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct SearchResultV2 { +pub struct SearchResult { /// An array of matched Accounts. pub accounts: Vec<Account>, /// An array of matched Statuses. diff --git a/src/entities/status.rs b/src/entities/status.rs index 2de5d26..a094b0c 100644 --- a/src/entities/status.rs +++ b/src/entities/status.rs @@ -95,6 +95,10 @@ pub struct Tag { pub name: String, /// The URL of the hashtag. pub url: String, + /// Usage statistics for given days (typically the past week). + pub history: Vec<TagHistory>, + /// Whether the current token’s authorized user is following this tag. + pub following: Option<bool>, } /// Application details. @@ -105,3 +109,14 @@ pub struct Application { /// Homepage URL of the application. pub website: Option<String>, } + +/// Usage statistics for given days (typically the past week). +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TagHistory { + /// UNIX timestamp on midnight of the given day. + pub day: String, + /// The counted usage of the tag within that day. + pub uses: String, + /// The total of accounts using the tag within that day. + pub accounts: String, +} 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/log.rs b/src/helpers/log.rs index 147e98c..31f78cd 100644 --- a/src/helpers/log.rs +++ b/src/helpers/log.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; /// Log metadata about this request based on the type given: /// /// ```no_run -/// use elefren::log_serde; +/// use mastodon_async::log_serde; /// tokio_test::block_on(async { /// let request = reqwest::get("https://example.org/").await.unwrap(); /// log::warn!( diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index f6a3b4f..5fb3512 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -4,7 +4,7 @@ /// In order to use this module, set the "toml" feature in your Cargo.toml: /// /// ```toml,ignore -/// [dependencies.elefren] +/// [dependencies.mastodon-async] /// version = "0.22" /// features = ["toml"] /// ``` @@ -28,7 +28,7 @@ pub mod json; /// In order to use this module, set the "env" feature in your Cargo.toml: /// /// ```toml,ignore -/// [dependencies.elefren] +/// [dependencies.mastodon-async] /// version = "0.22" /// features = ["env"] /// ``` 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, + }) + } } @@ -1,14 +1,14 @@ -//! # Elefren: API Wrapper around the Mastodon API. +//! # mastodon-async: API Wrapper around the Mastodon API. //! //! Most of the api is documented on [Mastodon's website](https://docs.joinmastodon.org/client/intro/) //! //! ```no_run -//! use elefren::{helpers::cli, prelude::*}; +//! use mastodon_async::{helpers::cli, prelude::*}; //! use futures_util::StreamExt; //! //! tokio_test::block_on(async { //! let registration = Registration::new("https://botsin.space") -//! .client_name("elefren_test") +//! .client_name("mastodon-async_test") //! .build() //! .await //! .unwrap(); @@ -28,12 +28,12 @@ //! }); //! ``` //! -//! Elefren also supports Mastodon's Streaming API: +//! mastodon-async also supports Mastodon's Streaming API: //! //! ## Example //! //! ```no_run -//! use elefren::{prelude::*, entities::event::Event}; +//! use mastodon_async::{prelude::*, entities::event::Event}; //! use futures_util::TryStreamExt; //! //! let data = Data::default(); diff --git a/src/macros.rs b/src/macros.rs index 2a74c85..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 } } )+ @@ -50,28 +40,20 @@ macro_rules! paged_routes { "`\n# Errors\nIf `access_token` is not set.", "\n", "```no_run", - "use elefren::prelude::*;\n", + "use mastodon_async::prelude::*;\n", "let data = Data::default();\n", "let client = Mastodon::from(data);\n", "client.", stringify!($name), "();\n", "```" ), 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 } } @@ -352,7 +364,7 @@ macro_rules! route { "`\n# Errors\nIf `access_token` is not set.", "\n", "```no_run", - "use elefren::prelude::*;\n", + "use mastodon_async::prelude::*;\n", "let data = Data::default();\n", "let client = Mastodon::from(data);\n", "client.", stringify!($name), "();\n", @@ -380,7 +392,7 @@ macro_rules! route_id { "`\n# Errors\nIf `access_token` is not set.", "\n", "```no_run", - "use elefren::prelude::*;\n", + "use mastodon_async::prelude::*;\n", "let data = Data::default();\n", "let client = Mastodon::from(data);\n", "client.", stringify!($name), "(\"42\");\n", @@ -406,30 +418,22 @@ macro_rules! paged_routes_with_id { "`\n# Errors\nIf `access_token` is not set.", "\n", "```no_run", - "use elefren::prelude::*;\n", + "use mastodon_async::prelude::*;\n", "let data = Data::default();", "let client = Mastodon::from(data);\n", "client.", stringify!($name), "(\"some-id\");\n", "```" ), 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 } } @@ -446,8 +450,8 @@ macro_rules! streaming { $desc, "\n\nExample:\n\n", " -use elefren::prelude::*; -use elefren::entities::event::Event; +use mastodon_async::prelude::*; +use mastodon_async::entities::event::Event; use futures_util::{pin_mut, StreamExt, TryStreamExt}; tokio_test::block_on(async { @@ -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)* } @@ -486,8 +496,8 @@ tokio_test::block_on(async { $desc, "\n\nExample:\n\n", " -use elefren::prelude::*; -use elefren::entities::event::Event; +use mastodon_async::prelude::*; +use mastodon_async::entities::event::Event; use futures_util::{pin_mut, StreamExt, TryStreamExt}; tokio_test::block_on(async { @@ -513,13 +523,19 @@ tokio_test::block_on(async { 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?; + let response = self.authenticated(self.client.get(url.as_str())).header("Accept", "application/json").send().await?; debug!( status = log_serde!(response Status), url = as_debug!(url), |