From cb3f9745b193755d1e53d41e43093a2269af8320 Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Mon, 25 Jan 2016 20:39:10 -0500 Subject: [PATCH 1/6] Add Serde dependency --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ae81c9c..5f6aea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ readme = "README.md" chrono = "0.2.17" hyper = "0.7.0" rustc-serialize = "0.3.16" +serde = "0.6.1" url = "0.5.0" [dev-dependencies] From 99c7481698406178ecd1a165816b3d49db652c43 Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Mon, 25 Jan 2016 20:52:55 -0500 Subject: [PATCH 2/6] Implement Serialize and Deserialize for Static --- src/lib.rs | 1 + src/token/statik.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ffad986..3a2d01c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,7 @@ extern crate chrono; extern crate hyper; extern crate rustc_serialize; +extern crate serde; extern crate url; pub use token::{Token, Lifetime}; diff --git a/src/token/statik.rs b/src/token/statik.rs index 3180588..620ecdb 100644 --- a/src/token/statik.rs +++ b/src/token/statik.rs @@ -1,4 +1,6 @@ use rustc_serialize::json::Json; +use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde::de::impls::UnitVisitor; use super::Lifetime; use client::response::{FromResponse, ParseError, JsonHelper}; @@ -21,6 +23,19 @@ impl FromResponse for Static { } } +impl Serialize for Static { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + serializer.visit_unit_struct("Static") + } +} + +impl Deserialize for Static { + fn deserialize(deserializer: &mut D) -> Result { + deserializer.visit_unit_struct("Static", UnitVisitor) + .and(Ok(Static)) + } +} + #[cfg(test)] mod tests { use rustc_serialize::json::Json; From bdd3cd8a68a704a27d6ef67262a48a433b135b1b Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Mon, 25 Jan 2016 22:23:16 -0500 Subject: [PATCH 3/6] Implement Serialize and Deserialize for Expiring --- src/token/expiring.rs | 87 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/token/expiring.rs b/src/token/expiring.rs index 0ad8a1c..b56caad 100644 --- a/src/token/expiring.rs +++ b/src/token/expiring.rs @@ -1,6 +1,8 @@ use chrono::{DateTime, UTC, Duration, TimeZone}; use rustc_serialize::json::Json; use rustc_serialize::{Encodable, Encoder, Decodable, Decoder}; +use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde::{ser, de}; use super::Lifetime; use client::response::{FromResponse, ParseError, JsonHelper}; @@ -91,6 +93,91 @@ impl Decodable for Expiring { } } +impl Serialize for Expiring { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + serializer.visit_struct("Expiring", SerVisitor(self, 0)) + } +} + +struct SerVisitor<'a>(&'a Expiring, u8); +impl<'a> ser::MapVisitor for SerVisitor<'a> { + fn visit(&mut self, serializer: &mut S) -> Result, S::Error> { + self.1 += 1; + match self.1 { + 1 => serializer.visit_struct_elt("refresh_token", &self.0.refresh_token).map(Some), + 2 => serializer.visit_struct_elt("expires", &self.0.expires.timestamp()).map(Some), + _ => Ok(None), + } + } + + fn len(&self) -> Option { Some(2) } +} + +impl Deserialize for Expiring { + fn deserialize(deserializer: &mut D) -> Result { + static FIELDS: &'static [&'static str] = &["refresh_token", "expires"]; + deserializer.visit_struct("Expiring", FIELDS, DeVisitor) + } +} + +struct DeVisitor; +impl de::Visitor for DeVisitor { + type Value = Expiring; + + fn visit_map(&mut self, mut visitor: V) -> Result { + let mut refresh_token = None; + let mut expires = None; + + loop { + match try!(visitor.visit_key()) { + Some(Field::RefreshToken) => refresh_token = Some(try!(visitor.visit_value())), + Some(Field::Expires) => expires = Some(try!(visitor.visit_value())), + None => break, + } + } + + let refresh_token = match refresh_token { + Some(s) => s, + None => return visitor.missing_field("refresh_token"), + }; + let expires = match expires { + Some(i) => UTC.timestamp(i, 0), + None => return visitor.missing_field("expires"), + }; + + try!(visitor.end()); + + Ok(Expiring { + refresh_token: refresh_token, + expires: expires, + }) + } +} + +enum Field { + RefreshToken, + Expires, +} + +impl Deserialize for Field { + fn deserialize(deserializer: &mut D) -> Result { + deserializer.visit(FieldVisitor) + } +} + +struct FieldVisitor; +impl de::Visitor for FieldVisitor { + type Value = Field; + + fn visit_str(&mut self, value: &str) -> Result { + match value { + "refresh_token" => Ok(Field::RefreshToken), + "expires" => Ok(Field::Expires), + _ => Err(de::Error::syntax("expected refresh_token or expires")), + } + } +} + #[cfg(test)] mod tests { use chrono::{UTC, Duration, Timelike}; From ba5b74c69861494f83f1fa529019978d0ffe57bf Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Mon, 25 Jan 2016 22:50:15 -0500 Subject: [PATCH 4/6] Implement Serialize and Deserialize for Bearer --- src/token/bearer.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/token/bearer.rs b/src/token/bearer.rs index 781162f..f65a498 100644 --- a/src/token/bearer.rs +++ b/src/token/bearer.rs @@ -1,5 +1,9 @@ +use std::marker::PhantomData; + use hyper::header; use rustc_serialize::json::Json; +use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde::{ser, de}; use super::{Token, Lifetime}; use client::response::{FromResponse, ParseError, JsonHelper}; @@ -58,6 +62,97 @@ impl FromResponse for Bearer { } } +impl Serialize for Bearer { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + serializer.visit_struct("Bearer", SerVisitor(self, 0)) + } +} + +struct SerVisitor<'a, L: Lifetime + Serialize + 'a>(&'a Bearer, u8); +impl<'a, L: Lifetime + Serialize + 'a> ser::MapVisitor for SerVisitor<'a, L> { + fn visit(&mut self, serializer: &mut S) -> Result, S::Error> { + self.1 += 1; + match self.1 { + 1 => serializer.visit_struct_elt("access_token", &self.0.access_token).map(Some), + 2 => serializer.visit_struct_elt("scope", &self.0.scope).map(Some), + 3 => serializer.visit_struct_elt("lifetime", &self.0.lifetime).map(Some), + _ => Ok(None), + } + } + + fn len(&self) -> Option { Some(3) } +} + +impl Deserialize for Bearer { + fn deserialize(deserializer: &mut D) -> Result { + static FIELDS: &'static [&'static str] = &["access_token", "scope", "lifetime"]; + deserializer.visit_struct("Bearer", FIELDS, DeVisitor(PhantomData)) + } +} + +struct DeVisitor(PhantomData); +impl de::Visitor for DeVisitor { + type Value = Bearer; + + fn visit_map(&mut self, mut visitor: V) -> Result, V::Error> { + let mut access_token = None; + let mut scope = None; + let mut lifetime = None; + + loop { + match try!(visitor.visit_key()) { + Some(Field::AccessToken) => access_token = Some(try!(visitor.visit_value())), + Some(Field::Scope) => scope = Some(try!(visitor.visit_value())), + Some(Field::Lifetime) => lifetime = Some(try!(visitor.visit_value())), + None => break, + } + } + + let access_token = match access_token { + Some(s) => s, + None => return visitor.missing_field("access_token"), + }; + let lifetime = match lifetime { + Some(l) => l, + None => return visitor.missing_field("lifetime"), + }; + + try!(visitor.end()); + + Ok(Bearer { + access_token: access_token, + scope: scope, + lifetime: lifetime, + }) + } +} + +enum Field { + AccessToken, + Scope, + Lifetime, +} + +impl Deserialize for Field { + fn deserialize(deserializer: &mut D) -> Result { + deserializer.visit(FieldVisitor) + } +} + +struct FieldVisitor; +impl de::Visitor for FieldVisitor { + type Value = Field; + + fn visit_str(&mut self, value: &str) -> Result { + match value { + "access_token" => Ok(Field::AccessToken), + "scope" => Ok(Field::Scope), + "lifetime" => Ok(Field::Lifetime), + _ => Err(de::Error::syntax("expected access_token, scope or lifetime")), + } + } +} + #[cfg(test)] mod tests { use chrono::{UTC, Duration}; From 3cf2af86094ce4c1c69d0cb2d4feb77aeaf09f79 Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Mon, 25 Jan 2016 23:06:44 -0500 Subject: [PATCH 5/6] Test serde de/serialization of tokens --- Cargo.toml | 1 + src/lib.rs | 3 +++ src/token/bearer.rs | 13 +++++++++++++ src/token/expiring.rs | 12 ++++++++++++ src/token/statik.rs | 9 +++++++++ 5 files changed, 38 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 5f6aea0..8451ba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ serde = "0.6.1" url = "0.5.0" [dev-dependencies] +serde_json = "0.6.0" yup-hyper-mock = "1.3.2" diff --git a/src/lib.rs b/src/lib.rs index 3a2d01c..b87a54e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,3 +149,6 @@ pub mod token; pub mod provider; pub mod error; pub mod client; + +#[cfg(test)] +extern crate serde_json; diff --git a/src/token/bearer.rs b/src/token/bearer.rs index f65a498..94fd964 100644 --- a/src/token/bearer.rs +++ b/src/token/bearer.rs @@ -157,6 +157,7 @@ impl de::Visitor for FieldVisitor { mod tests { use chrono::{UTC, Duration}; use rustc_serialize::json::Json; + use serde_json; use client::response::{FromResponse, ParseError}; use token::{Static, Expiring}; @@ -258,4 +259,16 @@ mod tests { assert!(expiring.expires() > &UTC::now()); assert!(expiring.expires() <= &(UTC::now() + Duration::seconds(3600))); } + + #[test] + fn serialize_deserialize() { + let original = Bearer { + access_token: String::from("foo"), + scope: Some(String::from("bar")), + lifetime: Static, + }; + let serialized = serde_json::to_value(&original); + let deserialized = serde_json::from_value(serialized).unwrap(); + assert_eq!(original, deserialized); + } } diff --git a/src/token/expiring.rs b/src/token/expiring.rs index b56caad..5fb15c6 100644 --- a/src/token/expiring.rs +++ b/src/token/expiring.rs @@ -182,6 +182,7 @@ impl de::Visitor for FieldVisitor { mod tests { use chrono::{UTC, Duration, Timelike}; use rustc_serialize::json::{self, Json}; + use serde_json; use client::response::FromResponse; use super::Expiring; @@ -218,4 +219,15 @@ mod tests { let decoded = json::decode(&json).unwrap(); assert_eq!(expiring, decoded); } + + #[test] + fn serialize_deserialize() { + let original = Expiring { + refresh_token: String::from("foo"), + expires: UTC::now().with_nanosecond(0).unwrap(), + }; + let serialized = serde_json::to_value(&original); + let deserialized = serde_json::from_value(serialized).unwrap(); + assert_eq!(original, deserialized); + } } diff --git a/src/token/statik.rs b/src/token/statik.rs index 620ecdb..f95c751 100644 --- a/src/token/statik.rs +++ b/src/token/statik.rs @@ -39,6 +39,7 @@ impl Deserialize for Static { #[cfg(test)] mod tests { use rustc_serialize::json::Json; + use serde_json; use client::response::{FromResponse, ParseError}; use super::Static; @@ -57,4 +58,12 @@ mod tests { Static::from_response(&json).unwrap_err() ); } + + #[test] + fn serialize_deserialize() { + let original = Static; + let serialized = serde_json::to_value(&original); + let deserialized = serde_json::from_value(serialized).unwrap(); + assert_eq!(original, deserialized); + } } From 6808f6bada762d2e3babdce6a5b309d9f5832c1f Mon Sep 17 00:00:00 2001 From: Curtis McEnroe Date: Mon, 25 Jan 2016 23:14:11 -0500 Subject: [PATCH 6/6] Add Serde to "Persisting tokens" documentation --- src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b87a54e..d0c470e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,8 @@ //! //! ### Persisting tokens //! -//! All token types implement `Encodable` and `Decodable` from `rustc_serialize`. +//! All token types implement `Encodable` / `Decodable` from `rustc_serialize` and `Serialize` / +//! `Deserialize` from `serde`. //! //! ```no_run //! # extern crate inth_oauth2; @@ -122,6 +123,18 @@ //! let json = json::encode(&token).unwrap(); //! # } //! ``` +//! +//! ```no_run +//! # extern crate inth_oauth2; +//! extern crate serde_json; +//! # use inth_oauth2::Client; +//! # use inth_oauth2::provider::Google; +//! # fn main() { +//! # let client = Client::::new(Default::default(), "", "", None); +//! # let token = client.request_token("").unwrap(); +//! let json = serde_json::to_string(&token).unwrap(); +//! # } +//! ``` #![warn( missing_docs,