Commit 760a0760 authored by Ben Boeckel's avatar Ben Boeckel
Browse files

api: handle endpoints with body data better

parent 94261405
......@@ -31,6 +31,7 @@ chrono = { version = "~0.4", features = ["serde"] }
graphql_client = { version = "~0.8", optional = true }
serde = { version = "~1.0", features = ["derive"] }
serde_json = "^1.0"
serde_urlencoded = "~0.5"
[dev-dependencies]
itertools = { version = "~0.8" }
......@@ -79,6 +79,7 @@ pub use self::endpoint::Endpoint;
pub use self::endpoint::Pairs;
pub use self::error::ApiError;
pub use self::error::BodyError;
pub use self::ignore::ignore;
pub use self::ignore::Ignore;
......
......@@ -6,12 +6,12 @@
use std::borrow::Cow;
use reqwest::Method;
use reqwest::{header, Method};
use serde::de::DeserializeOwned;
use url::form_urlencoded::Serializer;
use url::UrlQuery;
use crate::api::{ApiError, Client, Query};
use crate::api::{ApiError, BodyError, Client, Query};
/// A type for managing query parameters.
pub type Pairs<'a> = Serializer<'a, UrlQuery<'a>>;
......@@ -27,9 +27,11 @@ pub trait Endpoint {
#[allow(unused_variables)]
fn add_parameters(&self, pairs: Pairs) {}
/// Form data for the endpoint.
fn form_data(&self) -> Vec<u8> {
Vec::new()
/// The body for the endpoint.
///
/// Returns the `Content-Encoding` header for the data as well as the data itself.
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
Ok(None)
}
}
......@@ -43,9 +45,12 @@ where
let mut url = client.rest_endpoint(&self.endpoint())?;
self.add_parameters(url.query_pairs_mut());
let req = client
.build_rest(self.method(), url)
.form(&self.form_data());
let req = client.build_rest(self.method(), url);
let req = if let Some((mime, data)) = self.body()? {
req.header(header::CONTENT_TYPE, mime).body(data)
} else {
req
};
let rsp = client.rest(req)?;
let status = rsp.status();
let v = serde_json::from_reader(rsp)?;
......
......@@ -13,6 +13,7 @@ pub use std::borrow::Cow;
pub use reqwest::Method;
pub use crate::api::BodyError;
pub use crate::api::Client;
pub use crate::api::Endpoint;
pub use crate::api::Pageable;
......
......@@ -11,6 +11,25 @@ use thiserror::Error;
use crate::api::PaginationError;
/// Errors which may occur when creating form data.
#[derive(Debug, Error)]
// TODO #[non_exhaustive]
pub enum BodyError {
/// Body data could not be serialized from form parameters.
#[error("failed to URL encode form parameters: {}", source)]
UrlEncoded {
/// The source of the error.
#[from]
source: serde_urlencoded::ser::Error,
},
/// This is here to force `_` matching right now.
///
/// **DO NOT USE**
#[doc(hidden)]
#[error("unreachable...")]
_NonExhaustive,
}
/// Errors which may occur when using API endpoints.
#[derive(Debug, Error)]
// TODO #[non_exhaustive]
......@@ -31,6 +50,13 @@ where
#[from]
source: url::ParseError,
},
/// Body data could not be created.
#[error("failed to create form data: {}", source)]
Body {
/// The source of the error.
#[from]
source: BodyError,
},
/// JSON deserialization from GitLab failed.
#[error("could not parse JSON response: {}", source)]
Json {
......
......@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use reqwest::header;
use crate::api::{ApiError, Client, Endpoint, Query};
/// A query modifier that ignores the data returned from an endpoint.
......@@ -28,9 +30,12 @@ where
let mut url = client.rest_endpoint(&self.endpoint.endpoint())?;
self.endpoint.add_parameters(url.query_pairs_mut());
let req = client
.build_rest(self.endpoint.method(), url)
.form(&self.endpoint.form_data());
let req = client.build_rest(self.endpoint.method(), url);
let req = if let Some((mime, data)) = self.endpoint.body()? {
req.header(header::CONTENT_TYPE, mime).body(data)
} else {
req
};
let rsp = client.rest(req)?;
if !rsp.status().is_success() {
let v = serde_json::from_reader(rsp)?;
......
......@@ -4,7 +4,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use reqwest::header::HeaderMap;
use reqwest::header::{self, HeaderMap};
use serde::de::DeserializeOwned;
use thiserror::Error;
use url::Url;
......@@ -198,6 +198,8 @@ where
let mut next_url = None;
let use_keyset_pagination = self.endpoint.use_keyset_pagination();
let body = self.endpoint.body()?;
loop {
let page_url = if let Some(url) = next_url.take() {
url
......@@ -220,6 +222,11 @@ where
};
let req = client.build_rest(self.endpoint.method(), page_url);
let req = if let Some((mime, data)) = body.as_ref() {
req.header(header::CONTENT_TYPE, *mime).body(data.clone())
} else {
req
};
let rsp = client.rest(req)?;
let status = rsp.status();
......
......@@ -4,6 +4,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use reqwest::header;
use crate::api::{ApiError, Client, Endpoint, Query};
/// A query modifier that returns the raw data from the endpoint.
......@@ -28,9 +30,12 @@ where
let mut url = client.rest_endpoint(&self.endpoint.endpoint())?;
self.endpoint.add_parameters(url.query_pairs_mut());
let req = client
.build_rest(self.endpoint.method(), url)
.form(&self.endpoint.form_data());
let req = client.build_rest(self.endpoint.method(), url);
let req = if let Some((mime, data)) = self.endpoint.body()? {
req.header(header::CONTENT_TYPE, mime).body(data)
} else {
req
};
let rsp = client.rest(req)?;
if !rsp.status().is_success() {
let v = serde_json::from_reader(rsp)?;
......
......@@ -73,8 +73,8 @@ where
self.endpoint.add_parameters(pairs);
}
fn form_data(&self) -> Vec<u8> {
self.endpoint.form_data()
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
self.endpoint.body()
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment