Skip to content

Commit 1c28a66

Browse files
committed
feat: Refactored and improved usability of dsn/auth types
1 parent ec34576 commit 1c28a66

File tree

7 files changed

+155
-33
lines changed

7 files changed

+155
-33
lines changed

src/auth.rs

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
use std::fmt;
22
use std::str::FromStr;
33

4+
use chrono::{DateTime, Utc};
5+
6+
use protocol;
7+
use utils::{datetime_to_timestamp, timestamp_to_datetime};
8+
use dsn::Dsn;
9+
410
/// Represents an auth header parsing error.
511
#[derive(Debug, Fail)]
612
pub enum AuthParseError {
@@ -22,9 +28,9 @@ pub enum AuthParseError {
2228
}
2329

2430
/// Represents an auth header.
25-
#[derive(Default, Debug)]
31+
#[derive(Debug)]
2632
pub struct Auth {
27-
timestamp: Option<f64>,
33+
timestamp: Option<DateTime<Utc>>,
2834
client: Option<String>,
2935
version: u16,
3036
key: String,
@@ -33,7 +39,7 @@ pub struct Auth {
3339

3440
impl Auth {
3541
/// Returns the unix timestamp the client defined
36-
pub fn timestamp(&self) -> Option<f64> {
42+
pub fn timestamp(&self) -> Option<DateTime<Utc>> {
3743
self.timestamp
3844
}
3945

@@ -57,8 +63,8 @@ impl Auth {
5763
self.secret.is_none()
5864
}
5965

60-
/// Returns the client's relay
61-
pub fn client_relay(&self) -> Option<&str> {
66+
/// Returns the client's agent
67+
pub fn client_agent(&self) -> Option<&str> {
6268
self.client.as_ref().map(|x| x.as_str())
6369
}
6470
}
@@ -71,7 +77,7 @@ impl fmt::Display for Auth {
7177
self.key, self.version
7278
)?;
7379
if let Some(ts) = self.timestamp {
74-
write!(f, ", sentry_timestamp={}", ts)?;
80+
write!(f, ", sentry_timestamp={}", datetime_to_timestamp(&ts))?;
7581
}
7682
if let Some(ref client) = self.client {
7783
write!(f, ", sentry_client={}", client)?;
@@ -87,7 +93,13 @@ impl FromStr for Auth {
8793
type Err = AuthParseError;
8894

8995
fn from_str(s: &str) -> Result<Auth, AuthParseError> {
90-
let mut rv = Auth::default();
96+
let mut rv = Auth {
97+
timestamp: None,
98+
client: None,
99+
version: protocol::LATEST,
100+
key: "".into(),
101+
secret: None,
102+
};
91103
let mut base_iter = s.splitn(2, ' ');
92104
if !base_iter
93105
.next()
@@ -101,7 +113,8 @@ impl FromStr for Auth {
101113
let mut kviter = item.trim().split('=');
102114
match (kviter.next(), kviter.next()) {
103115
(Some("sentry_timestamp"), Some(ts)) => {
104-
rv.timestamp = Some(ts.parse().map_err(|_| AuthParseError::InvalidTimestamp)?);
116+
let f: f64 = ts.parse().map_err(|_| AuthParseError::InvalidTimestamp)?;
117+
rv.timestamp = Some(timestamp_to_datetime(f));
105118
}
106119
(Some("sentry_client"), Some(client)) => {
107120
rv.client = Some(client.into());
@@ -130,27 +143,12 @@ impl FromStr for Auth {
130143
}
131144
}
132145

133-
#[test]
134-
fn test_auth_parsing() {
135-
let auth: Auth = "Sentry sentry_timestamp=1328055286.51, \
136-
sentry_client=raven-python/42, \
137-
sentry_version=6, \
138-
sentry_key=public, \
139-
sentry_secret=secret"
140-
.parse()
141-
.unwrap();
142-
assert_eq!(auth.timestamp(), Some(1328055286.51));
143-
assert_eq!(auth.client_relay(), Some("raven-python/42"));
144-
assert_eq!(auth.version(), 6);
145-
assert_eq!(auth.public_key(), "public");
146-
assert_eq!(auth.secret_key(), Some("secret"));
147-
148-
assert_eq!(
149-
auth.to_string(),
150-
"Sentry sentry_key=public, \
151-
sentry_version=6, \
152-
sentry_timestamp=1328055286.51, \
153-
sentry_client=raven-python/42, \
154-
sentry_secret=secret"
155-
);
146+
pub(crate) fn auth_from_dsn_and_client(dsn: &Dsn, client: Option<&str>) -> Auth {
147+
Auth {
148+
timestamp: Some(Utc::now()),
149+
client: client.map(|x| x.to_string()),
150+
version: protocol::LATEST,
151+
key: dsn.public_key().to_string(),
152+
secret: dsn.secret_key().map(|x| x.to_string()),
153+
}
156154
}

src/dsn.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::str::FromStr;
44
use url::Url;
55

66
use project_id::{ProjectId, ProjectIdParseError};
7+
use auth::{Auth, auth_from_dsn_and_client};
78

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

7374
impl Dsn {
75+
/// Converts the dsn into an auth object.
76+
pub fn to_auth(&self, client_agent: Option<&str>) -> Auth {
77+
auth_from_dsn_and_client(self, client_agent)
78+
}
79+
7480
/// Returns the scheme
7581
pub fn scheme(&self) -> Scheme {
7682
self.scheme

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ mod macros;
5555
mod auth;
5656
mod dsn;
5757
mod project_id;
58+
mod utils;
5859
pub mod protocol;
5960

6061
pub use auth::*;

src/protocol/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
33
pub mod v7;
44

5-
mod utils;
5+
/// The latest version of the protocol.
6+
pub const LATEST: u16 = 7;
67

78
/// the always latest sentry protocol version
89
pub mod latest {

src/protocol/v7.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use serde::de::{Deserialize, Deserializer, Error as DeError};
1515
use serde::ser::{Error as SerError, Serialize, SerializeMap, Serializer};
1616
use serde_json::{from_value, to_value};
1717

18-
use protocol::utils::ts_seconds_float;
18+
use utils::ts_seconds_float;
1919

2020
/// An arbitrary (JSON) value (`serde_json::value::Value`)
2121
pub mod value {

src/utils.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use chrono::{DateTime, Utc, TimeZone};
2+
3+
/// Converts a datetime object into a float timestamp.
4+
pub fn datetime_to_timestamp(dt: &DateTime<Utc>) -> f64 {
5+
if dt.timestamp_subsec_nanos() == 0 {
6+
dt.timestamp() as f64
7+
} else {
8+
(dt.timestamp() as f64) +
9+
((dt.timestamp_subsec_micros() as f64) / 1_000_000f64)
10+
}
11+
}
12+
13+
pub fn timestamp_to_datetime(ts: f64) -> DateTime<Utc> {
14+
let secs = ts as i64;
15+
let micros = (ts.fract() * 1_000_000f64) as u32;
16+
Utc.timestamp_opt(secs, micros * 1000).unwrap()
17+
}
18+
19+
20+
pub mod ts_seconds_float {
21+
use std::fmt;
22+
use serde::{ser, de};
23+
use chrono::{DateTime, Utc, TimeZone};
24+
25+
use super::timestamp_to_datetime;
26+
27+
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
28+
where D: de::Deserializer<'de>
29+
{
30+
Ok(d.deserialize_any(SecondsTimestampVisitor)
31+
.map(|dt| dt.with_timezone(&Utc))?)
32+
}
33+
34+
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
35+
where S: ser::Serializer
36+
{
37+
if dt.timestamp_subsec_nanos() == 0 {
38+
serializer.serialize_i64(dt.timestamp())
39+
} else {
40+
serializer.serialize_f64(
41+
(dt.timestamp() as f64) +
42+
((dt.timestamp_subsec_micros() as f64) / 1_000_000f64)
43+
)
44+
}
45+
}
46+
47+
struct SecondsTimestampVisitor;
48+
49+
impl<'de> de::Visitor<'de> for SecondsTimestampVisitor {
50+
type Value = DateTime<Utc>;
51+
52+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result
53+
{
54+
write!(formatter, "a unix timestamp")
55+
}
56+
57+
fn visit_f64<E>(self, value: f64) -> Result<DateTime<Utc>, E>
58+
where E: de::Error
59+
{
60+
Ok(timestamp_to_datetime(value))
61+
}
62+
63+
fn visit_i64<E>(self, value: i64) -> Result<DateTime<Utc>, E>
64+
where E: de::Error
65+
{
66+
Ok(Utc.timestamp_opt(value, 0).unwrap())
67+
}
68+
69+
fn visit_u64<E>(self, value: u64) -> Result<DateTime<Utc>, E>
70+
where E: de::Error
71+
{
72+
Ok(Utc.timestamp_opt(value as i64, 0).unwrap())
73+
}
74+
}
75+
}

tests/test_auth.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
extern crate chrono;
2+
extern crate sentry_types;
3+
use chrono::{TimeZone, Utc};
4+
use sentry_types::{Auth, Dsn, protocol};
5+
6+
7+
#[test]
8+
fn test_auth_parsing() {
9+
let auth: Auth = "Sentry sentry_timestamp=1328055286.5, \
10+
sentry_client=raven-python/42, \
11+
sentry_version=6, \
12+
sentry_key=public, \
13+
sentry_secret=secret"
14+
.parse()
15+
.unwrap();
16+
assert_eq!(auth.timestamp(), Some(Utc.ymd(2012, 2, 1).and_hms_milli(0, 14, 46, 500)));
17+
assert_eq!(auth.client_agent(), Some("raven-python/42"));
18+
assert_eq!(auth.version(), 6);
19+
assert_eq!(auth.public_key(), "public");
20+
assert_eq!(auth.secret_key(), Some("secret"));
21+
22+
assert_eq!(
23+
auth.to_string(),
24+
"Sentry sentry_key=public, \
25+
sentry_version=6, \
26+
sentry_timestamp=1328055286.5, \
27+
sentry_client=raven-python/42, \
28+
sentry_secret=secret"
29+
);
30+
}
31+
32+
#[test]
33+
fn auth_to_dsn() {
34+
let url = "https://username:password@domain:8888/23";
35+
let dsn = url.parse::<Dsn>().unwrap();
36+
let auth = dsn.to_auth(Some("sentry-rust/1.0"));
37+
assert_eq!(auth.client_agent(), Some("sentry-rust/1.0"));
38+
assert_eq!(auth.version(), protocol::LATEST);
39+
assert_eq!(auth.public_key(), "username");
40+
assert_eq!(auth.secret_key(), Some("password"));
41+
}

0 commit comments

Comments
 (0)