about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2019-02-26T12·46+0100
committerVincent Ambo <mail@tazj.in>2019-02-26T12·46+0100
commit479a6b3442a852d414d977cdfabf61ad8180d877 (patch)
tree329ef63a3c10f94283e917463b38bf025137c797
parentb28f6e748d911b23a6c5c2c6badb33e079989517 (diff)
feat: Support JSON body serialization in requests & responses
Uses `serde_json` to provide serialisation and deserialisation of
request/response bodies.
-rw-r--r--Cargo.toml2
-rw-r--r--src/lib.rs77
2 files changed, 66 insertions, 13 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 076a3ff196..5f54546a39 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,3 +5,5 @@ authors = ["Vincent Ambo <mail@tazj.in>"]
 
 [dependencies]
 curl = "0.4"
+serde = "1.0"
+serde_json = "1.0"
diff --git a/src/lib.rs b/src/lib.rs
index 2280f9686b..417eebfd00 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,12 +12,19 @@
 //! [reqwest]: https://docs.rs/reqwest
 
 extern crate curl;
+extern crate serde;
+extern crate serde_json;
 
 use curl::easy::{Easy, List, ReadError};
+use serde::Serialize;
+use serde::de::DeserializeOwned;
 use std::collections::HashMap;
 use std::io::Write;
 use std::string::{FromUtf8Error, ToString};
 
+#[cfg(test)]
+mod tests;
+
 type CurlResult<T> = Result<T, curl::Error>;
 
 /// HTTP method to use for the request.
@@ -25,11 +32,19 @@ pub enum Method {
     Get, Post, Put, Patch, Delete
 }
 
-
 pub struct Request<'a> {
     handle: Easy,
     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)]
@@ -56,7 +71,7 @@ impl <'a> Request<'a> {
         Ok(Request {
             handle,
             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
     /// Content-Type.
-    pub fn body(&'a mut self, content_type: &str, body: &'a [u8])
-                    -> CurlResult<&mut Self> {
-        self.header("Content-Type", content_type)?;
-        self.body = Some(body);
+    pub fn body(&'a mut self, content_type: &'a str, data: &'a [u8]) -> &mut Self {
+        self.body = Body::Bytes { data, content_type };
+        self
+    }
 
+    /// 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)
     }
 
@@ -89,6 +109,17 @@ impl <'a> Request<'a> {
         let mut headers = HashMap::new();
         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
             // possible to write data into the above local buffers without
@@ -96,13 +127,22 @@ impl <'a> Request<'a> {
             let mut transfer = self.handle.transfer();
 
             // Write the payload if it exists:
-            if let Some(body) = self.body {
-                transfer.read_function(move |mut into| {
-                    into.write_all(body)
-                        .map(|_| body.len())
+            match self.body {
+                Body::Bytes { data, .. } => transfer.read_function(move |mut into| {
+                    into.write_all(data)
+                        .map(|_| data.len())
+                        .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
             // terminated if any header is malformed:
@@ -159,4 +199,15 @@ impl CurlResponse<Vec<u8>> {
             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,
+        })
+    }
 }