From 46871da4c12cb885b067f6745923232a1e591bff Mon Sep 17 00:00:00 2001 From: Paul Woolcock Date: Mon, 27 Aug 2018 07:42:36 -0400 Subject: [PATCH] feat(helpers): add json helper module --- Cargo.toml | 3 +- src/errors.rs | 6 +- src/helpers/json.rs | 231 ++++++++++++++++++++++++++++++++++++++++++++ src/helpers/mod.rs | 12 +++ src/helpers/toml.rs | 2 +- src/lib.rs | 11 ++- 6 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 src/helpers/json.rs diff --git a/Cargo.toml b/Cargo.toml index 67b2f4e..cb8ba05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ features = ["serde"] [features] default-features = [] -all = ["toml"] +json = [] +all = ["toml", "json"] [build-dependencies] skeptic = "0.13.3" diff --git a/src/errors.rs b/src/errors.rs index 5dee679..e03279b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,7 +1,7 @@ use std::{error, fmt, io::Error as IoError}; -use json::Error as SerdeError; use reqwest::{Error as HttpError, StatusCode}; +use serde_json::Error as SerdeError; #[cfg(feature = "toml")] use tomlcrate::de::Error as TomlDeError; #[cfg(feature = "toml")] @@ -119,8 +119,8 @@ from! { #[cfg(test)] mod tests { use super::*; - use json; use reqwest; + use serde_json; use std::io; macro_rules! assert_is { @@ -148,7 +148,7 @@ mod tests { #[test] fn from_serde_error() { - let err: SerdeError = json::from_str::<()>("not valid json").unwrap_err(); + let err: SerdeError = serde_json::from_str::<()>("not valid json").unwrap_err(); let err: Error = Error::from(err); assert_is!(err, Error::Serde(..)); } diff --git a/src/helpers/json.rs b/src/helpers/json.rs new file mode 100644 index 0000000..ab69890 --- /dev/null +++ b/src/helpers/json.rs @@ -0,0 +1,231 @@ +use std::{ + fs::{File, OpenOptions}, + io::{BufWriter, Read, Write}, + path::Path, +}; + +use serde_json; + +use data::Data; +use Result; + +/// Attempts to deserialize a Data struct from a string +pub fn from_str(s: &str) -> Result { + Ok(serde_json::from_str(s)?) +} + +/// Attempts to deserialize a Data struct from a slice of bytes +pub fn from_slice(s: &[u8]) -> Result { + Ok(serde_json::from_slice(s)?) +} + +/// Attempts to deserialize a Data struct from something that implements +/// the std::io::Read trait +pub fn from_reader(mut r: R) -> Result { + let mut buffer = Vec::new(); + r.read_to_end(&mut buffer)?; + from_slice(&buffer) +} + +/// Attempts to deserialize a Data struct from a file +pub fn from_file>(path: P) -> Result { + let path = path.as_ref(); + let file = File::open(path)?; + Ok(from_reader(file)?) +} + +/// Attempts to serialize a Data struct to a String +pub fn to_string(data: &Data) -> Result { + Ok(serde_json::to_string_pretty(data)?) +} + +/// Attempts to serialize a Data struct to a Vec of bytes +pub fn to_vec(data: &Data) -> Result> { + Ok(serde_json::to_vec(data)?) +} + +/// Attempts to serialize a Data struct to something that implements the +/// std::io::Write trait +pub fn to_writer(data: &Data, writer: W) -> Result<()> { + let mut buf_writer = BufWriter::new(writer); + let vec = to_vec(data)?; + buf_writer.write(&vec)?; + Ok(()) +} + +/// Attempts to serialize a Data struct to a file +/// +/// When opening the file, this will set the `.write(true)` and +/// `.truncate(true)` options, use the next method for more +/// fine-grained control +pub fn to_file>(data: &Data, path: P) -> Result<()> { + let mut options = OpenOptions::new(); + options.create(true).write(true).truncate(true); + to_file_with_options(data, path, options)?; + Ok(()) +} + +/// Attempts to serialize a Data struct to a file +pub fn to_file_with_options>( + data: &Data, + path: P, + options: OpenOptions, +) -> Result<()> { + let path = path.as_ref(); + let file = options.open(path)?; + to_writer(data, file)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{fs::OpenOptions, io::Cursor}; + use tempfile::{tempdir, NamedTempFile}; + + const DOC: &'static str = indoc!( + r#" + { + "base": "https://example.com", + "client_id": "adbc01234", + "client_secret": "0987dcba", + "redirect": "urn:ietf:wg:oauth:2.0:oob", + "token": "fedc5678" + } + "# + ); + + #[test] + fn test_from_str() { + let desered = from_str(DOC).expect("Couldn't deserialize Data"); + assert_eq!( + desered, + Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + } + ); + } + #[test] + fn test_from_slice() { + let doc = DOC.as_bytes(); + let desered = from_slice(&doc).expect("Couldn't deserialize Data"); + assert_eq!( + desered, + Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + } + ); + } + #[test] + fn test_from_reader() { + let doc = DOC.as_bytes(); + let doc = Cursor::new(doc); + let desered = from_reader(doc).expect("Couldn't deserialize Data"); + assert_eq!( + desered, + Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + } + ); + } + #[test] + fn test_from_file() { + let mut datafile = NamedTempFile::new().expect("Couldn't create tempfile"); + write!(&mut datafile, "{}", DOC).expect("Couldn't write Data to file"); + let desered = from_file(datafile.path()).expect("Couldn't deserialize Data"); + assert_eq!( + desered, + Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + } + ); + } + #[test] + fn test_to_string() { + let data = Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + }; + let s = to_string(&data).expect("Couldn't serialize Data"); + let desered = from_str(&s).expect("Couldn't deserialize Data"); + assert_eq!(data, desered); + } + #[test] + fn test_to_vec() { + let data = Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + }; + let v = to_vec(&data).expect("Couldn't write to vec"); + let desered = from_slice(&v).expect("Couldn't deserialize data"); + assert_eq!(data, desered); + } + #[test] + fn test_to_writer() { + let data = Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + }; + let mut buffer = Vec::new(); + to_writer(&data, &mut buffer).expect("Couldn't write to writer"); + let reader = Cursor::new(buffer); + let desered = from_reader(reader).expect("Couldn't deserialize Data"); + assert_eq!(data, desered); + } + #[test] + fn test_to_file() { + let data = Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + }; + let tempdir = tempdir().expect("Couldn't create tempdir"); + let filename = tempdir.path().join("mastodon-data.json"); + to_file(&data, &filename).expect("Couldn't write to file"); + let desered = from_file(&filename).expect("Couldn't deserialize Data"); + assert_eq!(data, desered); + } + #[test] + fn test_to_file_with_options() { + let data = Data { + base: "https://example.com".into(), + client_id: "adbc01234".into(), + client_secret: "0987dcba".into(), + redirect: "urn:ietf:wg:oauth:2.0:oob".into(), + token: "fedc5678".into(), + }; + let file = NamedTempFile::new().expect("Couldn't create tempfile"); + let mut options = OpenOptions::new(); + options.write(true).create(false).truncate(true); + to_file_with_options(&data, file.path(), options).expect("Couldn't write to file"); + let desered = from_file(file.path()).expect("Couldn't deserialize Data"); + assert_eq!(data, desered); + } +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index de0cfbf..1d24774 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -9,3 +9,15 @@ /// features = ["toml"] /// ``` pub mod toml; + +#[cfg(feature = "json")] +/// Helpers for serializing to/deserializing from json +/// +/// In order to use this module, set the "json" feature in your Cargo.toml: +/// +/// ```toml,ignore +/// [dependencies.elefen] +/// version = "0.12" +/// features = ["json"] +/// ``` +pub mod json; diff --git a/src/helpers/toml.rs b/src/helpers/toml.rs index ec3b026..cf20b6f 100644 --- a/src/helpers/toml.rs +++ b/src/helpers/toml.rs @@ -157,7 +157,6 @@ mod tests { } #[test] fn test_from_file() { - let mut datafile = NamedTempFile::new().expect("Couldn't create tempfile"); let doc = indoc!( r#" base = "https://example.com" @@ -167,6 +166,7 @@ mod tests { token = "fedc5678" "# ); + let mut datafile = NamedTempFile::new().expect("Couldn't create tempfile"); write!(&mut datafile, "{}", doc).expect("Couldn't write Data to file"); let desered = from_file(datafile.path()).expect("Couldn't deserialize Data"); assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index 3a570c9..e589606 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,7 @@ extern crate serde_derive; #[macro_use] extern crate doc_comment; #[macro_use] -extern crate serde_json as json; +extern crate serde_json; extern crate chrono; extern crate reqwest; extern crate serde; @@ -57,7 +57,10 @@ extern crate toml as tomlcrate; extern crate tempfile; #[cfg(test)] -#[cfg_attr(all(test, feature = "toml"), macro_use)] +#[cfg_attr( + all(test, any(feature = "toml", feature = "json")), + macro_use +)] extern crate indoc; use std::{borrow::Cow, ops}; @@ -567,12 +570,12 @@ fn deserialise serde::Deserialize<'de>>(mut response: Response) -> R let mut vec = Vec::new(); response.read_to_end(&mut vec)?; - match json::from_slice(&vec) { + match serde_json::from_slice(&vec) { Ok(t) => Ok(t), // If deserializing into the desired type fails try again to // see if this is an error response. Err(e) => { - if let Ok(error) = json::from_slice(&vec) { + if let Ok(error) = serde_json::from_slice(&vec) { return Err(Error::Api(error)); } Err(e.into())