Skip to content

Commit

Permalink
feat: Refactored and improved usability of dsn/auth types
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Mar 25, 2018
1 parent ec34576 commit 1c28a66
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 33 deletions.
60 changes: 29 additions & 31 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use std::fmt;
use std::str::FromStr;

use chrono::{DateTime, Utc};

use protocol;
use utils::{datetime_to_timestamp, timestamp_to_datetime};
use dsn::Dsn;

/// Represents an auth header parsing error.
#[derive(Debug, Fail)]
pub enum AuthParseError {
Expand All @@ -22,9 +28,9 @@ pub enum AuthParseError {
}

/// Represents an auth header.
#[derive(Default, Debug)]
#[derive(Debug)]
pub struct Auth {
timestamp: Option<f64>,
timestamp: Option<DateTime<Utc>>,
client: Option<String>,
version: u16,
key: String,
Expand All @@ -33,7 +39,7 @@ pub struct Auth {

impl Auth {
/// Returns the unix timestamp the client defined
pub fn timestamp(&self) -> Option<f64> {
pub fn timestamp(&self) -> Option<DateTime<Utc>> {
self.timestamp
}

Expand All @@ -57,8 +63,8 @@ impl Auth {
self.secret.is_none()
}

/// Returns the client's relay
pub fn client_relay(&self) -> Option<&str> {
/// Returns the client's agent
pub fn client_agent(&self) -> Option<&str> {
self.client.as_ref().map(|x| x.as_str())
}
}
Expand All @@ -71,7 +77,7 @@ impl fmt::Display for Auth {
self.key, self.version
)?;
if let Some(ts) = self.timestamp {
write!(f, ", sentry_timestamp={}", ts)?;
write!(f, ", sentry_timestamp={}", datetime_to_timestamp(&ts))?;
}
if let Some(ref client) = self.client {
write!(f, ", sentry_client={}", client)?;
Expand All @@ -87,7 +93,13 @@ impl FromStr for Auth {
type Err = AuthParseError;

fn from_str(s: &str) -> Result<Auth, AuthParseError> {
let mut rv = Auth::default();
let mut rv = Auth {
timestamp: None,
client: None,
version: protocol::LATEST,
key: "".into(),
secret: None,
};
let mut base_iter = s.splitn(2, ' ');
if !base_iter
.next()
Expand All @@ -101,7 +113,8 @@ impl FromStr for Auth {
let mut kviter = item.trim().split('=');
match (kviter.next(), kviter.next()) {
(Some("sentry_timestamp"), Some(ts)) => {
rv.timestamp = Some(ts.parse().map_err(|_| AuthParseError::InvalidTimestamp)?);
let f: f64 = ts.parse().map_err(|_| AuthParseError::InvalidTimestamp)?;
rv.timestamp = Some(timestamp_to_datetime(f));
}
(Some("sentry_client"), Some(client)) => {
rv.client = Some(client.into());
Expand Down Expand Up @@ -130,27 +143,12 @@ impl FromStr for Auth {
}
}

#[test]
fn test_auth_parsing() {
let auth: Auth = "Sentry sentry_timestamp=1328055286.51, \
sentry_client=raven-python/42, \
sentry_version=6, \
sentry_key=public, \
sentry_secret=secret"
.parse()
.unwrap();
assert_eq!(auth.timestamp(), Some(1328055286.51));
assert_eq!(auth.client_relay(), Some("raven-python/42"));
assert_eq!(auth.version(), 6);
assert_eq!(auth.public_key(), "public");
assert_eq!(auth.secret_key(), Some("secret"));

assert_eq!(
auth.to_string(),
"Sentry sentry_key=public, \
sentry_version=6, \
sentry_timestamp=1328055286.51, \
sentry_client=raven-python/42, \
sentry_secret=secret"
);
pub(crate) fn auth_from_dsn_and_client(dsn: &Dsn, client: Option<&str>) -> Auth {
Auth {
timestamp: Some(Utc::now()),
client: client.map(|x| x.to_string()),
version: protocol::LATEST,
key: dsn.public_key().to_string(),
secret: dsn.secret_key().map(|x| x.to_string()),
}
}
6 changes: 6 additions & 0 deletions src/dsn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::str::FromStr;
use url::Url;

use project_id::{ProjectId, ProjectIdParseError};
use auth::{Auth, auth_from_dsn_and_client};

/// Represents a dsn url parsing error.
#[derive(Debug, Fail)]
Expand Down Expand Up @@ -71,6 +72,11 @@ pub struct Dsn {
}

impl Dsn {
/// Converts the dsn into an auth object.
pub fn to_auth(&self, client_agent: Option<&str>) -> Auth {
auth_from_dsn_and_client(self, client_agent)
}

/// Returns the scheme
pub fn scheme(&self) -> Scheme {
self.scheme
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mod macros;
mod auth;
mod dsn;
mod project_id;
mod utils;
pub mod protocol;

pub use auth::*;
Expand Down
3 changes: 2 additions & 1 deletion src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
pub mod v7;

mod utils;
/// The latest version of the protocol.
pub const LATEST: u16 = 7;

/// the always latest sentry protocol version
pub mod latest {
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use serde::de::{Deserialize, Deserializer, Error as DeError};
use serde::ser::{Error as SerError, Serialize, SerializeMap, Serializer};
use serde_json::{from_value, to_value};

use protocol::utils::ts_seconds_float;
use utils::ts_seconds_float;

/// An arbitrary (JSON) value (`serde_json::value::Value`)
pub mod value {
Expand Down
75 changes: 75 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use chrono::{DateTime, Utc, TimeZone};

/// Converts a datetime object into a float timestamp.
pub fn datetime_to_timestamp(dt: &DateTime<Utc>) -> f64 {
if dt.timestamp_subsec_nanos() == 0 {
dt.timestamp() as f64
} else {
(dt.timestamp() as f64) +
((dt.timestamp_subsec_micros() as f64) / 1_000_000f64)
}
}

pub fn timestamp_to_datetime(ts: f64) -> DateTime<Utc> {
let secs = ts as i64;
let micros = (ts.fract() * 1_000_000f64) as u32;
Utc.timestamp_opt(secs, micros * 1000).unwrap()
}


pub mod ts_seconds_float {
use std::fmt;
use serde::{ser, de};
use chrono::{DateTime, Utc, TimeZone};

use super::timestamp_to_datetime;

pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
where D: de::Deserializer<'de>
{
Ok(d.deserialize_any(SecondsTimestampVisitor)
.map(|dt| dt.with_timezone(&Utc))?)
}

pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where S: ser::Serializer
{
if dt.timestamp_subsec_nanos() == 0 {
serializer.serialize_i64(dt.timestamp())
} else {
serializer.serialize_f64(
(dt.timestamp() as f64) +
((dt.timestamp_subsec_micros() as f64) / 1_000_000f64)
)
}
}

struct SecondsTimestampVisitor;

impl<'de> de::Visitor<'de> for SecondsTimestampVisitor {
type Value = DateTime<Utc>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
{
write!(formatter, "a unix timestamp")
}

fn visit_f64<E>(self, value: f64) -> Result<DateTime<Utc>, E>
where E: de::Error
{
Ok(timestamp_to_datetime(value))
}

fn visit_i64<E>(self, value: i64) -> Result<DateTime<Utc>, E>
where E: de::Error
{
Ok(Utc.timestamp_opt(value, 0).unwrap())
}

fn visit_u64<E>(self, value: u64) -> Result<DateTime<Utc>, E>
where E: de::Error
{
Ok(Utc.timestamp_opt(value as i64, 0).unwrap())
}
}
}
41 changes: 41 additions & 0 deletions tests/test_auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
extern crate chrono;
extern crate sentry_types;
use chrono::{TimeZone, Utc};
use sentry_types::{Auth, Dsn, protocol};


#[test]
fn test_auth_parsing() {
let auth: Auth = "Sentry sentry_timestamp=1328055286.5, \
sentry_client=raven-python/42, \
sentry_version=6, \
sentry_key=public, \
sentry_secret=secret"
.parse()
.unwrap();
assert_eq!(auth.timestamp(), Some(Utc.ymd(2012, 2, 1).and_hms_milli(0, 14, 46, 500)));
assert_eq!(auth.client_agent(), Some("raven-python/42"));
assert_eq!(auth.version(), 6);
assert_eq!(auth.public_key(), "public");
assert_eq!(auth.secret_key(), Some("secret"));

assert_eq!(
auth.to_string(),
"Sentry sentry_key=public, \
sentry_version=6, \
sentry_timestamp=1328055286.5, \
sentry_client=raven-python/42, \
sentry_secret=secret"
);
}

#[test]
fn auth_to_dsn() {
let url = "https://username:password@domain:8888/23";
let dsn = url.parse::<Dsn>().unwrap();
let auth = dsn.to_auth(Some("sentry-rust/1.0"));
assert_eq!(auth.client_agent(), Some("sentry-rust/1.0"));
assert_eq!(auth.version(), protocol::LATEST);
assert_eq!(auth.public_key(), "username");
assert_eq!(auth.secret_key(), Some("password"));
}

0 comments on commit 1c28a66

Please sign in to comment.