feat: Support JSON body serialization in requests & responses
Uses `serde_json` to provide serialisation and deserialisation of request/response bodies.
This commit is contained in:
parent
b28f6e748d
commit
479a6b3442
2 changed files with 66 additions and 13 deletions
|
@ -5,3 +5,5 @@ authors = ["Vincent Ambo <mail@tazj.in>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
curl = "0.4"
|
curl = "0.4"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
77
src/lib.rs
77
src/lib.rs
|
@ -12,12 +12,19 @@
|
||||||
//! [reqwest]: https://docs.rs/reqwest
|
//! [reqwest]: https://docs.rs/reqwest
|
||||||
|
|
||||||
extern crate curl;
|
extern crate curl;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
use curl::easy::{Easy, List, ReadError};
|
use curl::easy::{Easy, List, ReadError};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::string::{FromUtf8Error, ToString};
|
use std::string::{FromUtf8Error, ToString};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
type CurlResult<T> = Result<T, curl::Error>;
|
type CurlResult<T> = Result<T, curl::Error>;
|
||||||
|
|
||||||
/// HTTP method to use for the request.
|
/// HTTP method to use for the request.
|
||||||
|
@ -25,11 +32,19 @@ pub enum Method {
|
||||||
Get, Post, Put, Patch, Delete
|
Get, Post, Put, Patch, Delete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Request<'a> {
|
pub struct Request<'a> {
|
||||||
handle: Easy,
|
handle: Easy,
|
||||||
headers: List,
|
headers: List,
|
||||||
body: Option<&'a [u8]>,
|
body: Body<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Body<'a> {
|
||||||
|
NoBody,
|
||||||
|
Json(Vec<u8>),
|
||||||
|
Bytes {
|
||||||
|
content_type: &'a str,
|
||||||
|
data: &'a [u8],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -56,7 +71,7 @@ impl <'a> Request<'a> {
|
||||||
Ok(Request {
|
Ok(Request {
|
||||||
handle,
|
handle,
|
||||||
headers: List::new(),
|
headers: List::new(),
|
||||||
body: None,
|
body: Body::NoBody,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,11 +89,16 @@ impl <'a> Request<'a> {
|
||||||
|
|
||||||
/// Add a byte-array body to a request using the specified
|
/// Add a byte-array body to a request using the specified
|
||||||
/// Content-Type.
|
/// Content-Type.
|
||||||
pub fn body(&'a mut self, content_type: &str, body: &'a [u8])
|
pub fn body(&'a mut self, content_type: &'a str, data: &'a [u8]) -> &mut Self {
|
||||||
-> CurlResult<&mut Self> {
|
self.body = Body::Bytes { data, content_type };
|
||||||
self.header("Content-Type", content_type)?;
|
self
|
||||||
self.body = Some(body);
|
}
|
||||||
|
|
||||||
|
/// Add a JSON-encoded body from a serializable type.
|
||||||
|
pub fn json<T: Serialize>(&'a mut self, body: &T)
|
||||||
|
-> Result<&mut Self, serde_json::Error> {
|
||||||
|
let json = serde_json::to_vec(body)?;
|
||||||
|
self.body = Body::Json(json);
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +109,17 @@ impl <'a> Request<'a> {
|
||||||
let mut headers = HashMap::new();
|
let mut headers = HashMap::new();
|
||||||
let mut body = vec![];
|
let mut body = vec![];
|
||||||
|
|
||||||
|
// Optionally set content type if a body payload is
|
||||||
|
// configured.
|
||||||
|
match self.body {
|
||||||
|
Body::Json(..) => self.header("Content-Type", "application/json"),
|
||||||
|
Body::Bytes { content_type, .. } => self.header("Content-Type", content_type),
|
||||||
|
Body::NoBody => Ok(&mut self),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Configure headers on the request:
|
||||||
|
self.handle.http_headers(self.headers)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Take a scoped transfer from the Easy handle. This makes it
|
// Take a scoped transfer from the Easy handle. This makes it
|
||||||
// possible to write data into the above local buffers without
|
// possible to write data into the above local buffers without
|
||||||
|
@ -96,13 +127,22 @@ impl <'a> Request<'a> {
|
||||||
let mut transfer = self.handle.transfer();
|
let mut transfer = self.handle.transfer();
|
||||||
|
|
||||||
// Write the payload if it exists:
|
// Write the payload if it exists:
|
||||||
if let Some(body) = self.body {
|
match self.body {
|
||||||
transfer.read_function(move |mut into| {
|
Body::Bytes { data, .. } => transfer.read_function(move |mut into| {
|
||||||
into.write_all(body)
|
into.write_all(data)
|
||||||
.map(|_| body.len())
|
.map(|_| data.len())
|
||||||
.map_err(|_| ReadError::Abort)
|
.map_err(|_| ReadError::Abort)
|
||||||
})?;
|
})?,
|
||||||
}
|
|
||||||
|
Body::Json(json) => transfer.read_function(move |mut into| {
|
||||||
|
into.write_all(&json)
|
||||||
|
.map(|_| json.len())
|
||||||
|
.map_err(|_| ReadError::Abort)
|
||||||
|
})?,
|
||||||
|
|
||||||
|
// Do nothing if there is no body ...
|
||||||
|
Body::NoBody => {},
|
||||||
|
};
|
||||||
|
|
||||||
// Read one header per invocation. Request processing is
|
// Read one header per invocation. Request processing is
|
||||||
// terminated if any header is malformed:
|
// terminated if any header is malformed:
|
||||||
|
@ -159,4 +199,15 @@ impl CurlResponse<Vec<u8>> {
|
||||||
headers: self.headers,
|
headers: self.headers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to deserialize the HTTP response body from JSON.
|
||||||
|
pub fn as_json<T: DeserializeOwned>(self) -> Result<CurlResponse<T>, serde_json::Error> {
|
||||||
|
let deserialized = serde_json::from_slice(&self.body)?;
|
||||||
|
|
||||||
|
Ok(CurlResponse {
|
||||||
|
body: deserialized,
|
||||||
|
status: self.status,
|
||||||
|
headers: self.headers,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue