diff --git a/src/token/expiring.rs b/src/token/expiring.rs new file mode 100644 index 0000000..2e29067 --- /dev/null +++ b/src/token/expiring.rs @@ -0,0 +1,185 @@ +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}; + +/// An expiring token. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Expiring { + expires: DateTime, +} + +impl Expiring { + /// Returns the expiry time of the access token. + pub fn expires(&self) -> &DateTime { &self.expires } +} + +impl Lifetime for Expiring { + fn expired(&self) -> bool { self.expires < UTC::now() } +} + +impl FromResponse for Expiring { + fn from_response(json: &Json) -> Result { + let obj = try!(JsonHelper(json).as_object()); + + if obj.0.contains_key("refresh_token") { + return Err(ParseError::UnexpectedField("refresh_token")); + } + + let expires_in = try!(obj.get_i64("expires_in")); + + Ok(Expiring { + expires: UTC::now() + Duration::seconds(expires_in), + }) + } +} + +#[derive(RustcEncodable, RustcDecodable)] +struct Serializable { + expires: i64, +} + +impl<'a> From<&'a Expiring> for Serializable { + fn from(expiring: &Expiring) -> Self { + Serializable { + expires: expiring.expires.timestamp(), + } + } +} + +impl Into for Serializable { + fn into(self) -> Expiring { + Expiring { + expires: UTC.timestamp(self.expires, 0), + } + } +} + +impl Encodable for Expiring { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + Serializable::from(self).encode(s) + } +} + +impl Decodable for Expiring { + fn decode(d: &mut D) -> Result { + Serializable::decode(d).map(Into::into) + } +} + +impl Serialize for Expiring { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> { + serializer.serialize_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.serialize_struct_elt("expires", &self.0.expires.timestamp()).map(Some), + _ => Ok(None), + } + } + + fn len(&self) -> Option { Some(1) } +} + +impl Deserialize for Expiring { + fn deserialize(deserializer: &mut D) -> Result { + static FIELDS: &'static [&'static str] = &["expires"]; + deserializer.deserialize_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 expires = None; + + loop { + match try!(visitor.visit_key()) { + Some(Field::Expires) => expires = Some(try!(visitor.visit_value())), + None => break, + } + } + + let expires = match expires { + Some(i) => UTC.timestamp(i, 0), + None => return visitor.missing_field("expires"), + }; + + try!(visitor.end()); + + Ok(Expiring { + expires: expires, + }) + } +} + +enum Field { + Expires, +} + +impl Deserialize for Field { + fn deserialize(deserializer: &mut D) -> Result { + deserializer.deserialize(FieldVisitor) + } +} + +struct FieldVisitor; +impl de::Visitor for FieldVisitor { + type Value = Field; + + fn visit_str(&mut self, value: &str) -> Result { + match value { + "expires" => Ok(Field::Expires), + _ => Err(de::Error::custom("expected expires")), + } + } +} + +#[cfg(test)] +mod tests { + use chrono::{UTC, Duration, Timelike}; + use rustc_serialize::json::{self, Json}; + use serde_json; + + use client::response::FromResponse; + use super::Expiring; + + #[test] + fn from_response() { + let json = Json::from_str(r#"{"expires_in":3600}"#).unwrap(); + let expiring = Expiring::from_response(&json).unwrap(); + assert!(expiring.expires > UTC::now()); + assert!(expiring.expires <= UTC::now() + Duration::seconds(3600)); + } + + #[test] + fn encode_decode() { + let expiring = Expiring { + expires: UTC::now().with_nanosecond(0).unwrap(), + }; + let json = json::encode(&expiring).unwrap(); + let decoded = json::decode(&json).unwrap(); + assert_eq!(expiring, decoded); + } + + #[test] + fn serialize_deserialize() { + let original = Expiring { + 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/mod.rs b/src/token/mod.rs index 5dbbddc..a245975 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -35,5 +35,8 @@ mod bearer; pub use self::statik::Static; mod statik; +pub use self::expiring::Expiring; +mod expiring; + pub use self::refresh::Refresh; mod refresh;