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…
	
	Add table
		Add a link
		
	
		Reference in a new issue