Skip to content

Commit 62f7b8e

Browse files
authored
ref: Change the internal representation of project IDs from u64s to strings (#452)
Project IDs aren't guaranteed to be numbers forever, and shouldn't be treated as such any more, to be forward-compatible with whatever new identifier format that does get used in the future for project IDs. They're now treated as opaque strings internally, and are subjected to minimal validation due to their opacity.
1 parent 0fcb6a0 commit 62f7b8e

File tree

3 files changed

+40
-75
lines changed

3 files changed

+40
-75
lines changed

sentry-types/src/dsn.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ impl Dsn {
140140
}
141141

142142
/// Returns the project_id
143-
pub fn project_id(&self) -> ProjectId {
144-
self.project_id
143+
pub fn project_id(&self) -> &ProjectId {
144+
&self.project_id
145145
}
146146
}
147147

@@ -229,15 +229,15 @@ mod test {
229229

230230
#[test]
231231
fn test_dsn_parsing() {
232-
let url = "https://username:password@domain:8888/23";
232+
let url = "https://username:password@domain:8888/23%21";
233233
let dsn = url.parse::<Dsn>().unwrap();
234234
assert_eq!(dsn.scheme(), Scheme::Https);
235235
assert_eq!(dsn.public_key(), "username");
236236
assert_eq!(dsn.secret_key(), Some("password"));
237237
assert_eq!(dsn.host(), "domain");
238238
assert_eq!(dsn.port(), 8888);
239239
assert_eq!(dsn.path(), "/");
240-
assert_eq!(dsn.project_id(), ProjectId::new(23));
240+
assert_eq!(dsn.project_id(), &ProjectId::new("23%21"));
241241
assert_eq!(url, dsn.to_string());
242242
}
243243

@@ -303,9 +303,18 @@ mod test {
303303
}
304304

305305
#[test]
306-
#[should_panic(expected = "InvalidProjectId")]
306+
fn test_dsn_non_integer_project_id() {
307+
let url = "https://username:password@domain:8888/abc123youandme%21%21";
308+
let dsn = url.parse::<Dsn>().unwrap();
309+
assert_eq!(dsn.project_id(), &ProjectId::new("abc123youandme%21%21"));
310+
}
311+
312+
#[test]
307313
fn test_dsn_more_than_one_non_integer_path() {
308-
Dsn::from_str("http://username:@domain:8888/path/path2").unwrap();
314+
let url = "http://username:@domain:8888/pathone/pathtwo/pid";
315+
let dsn = url.parse::<Dsn>().unwrap();
316+
assert_eq!(dsn.project_id(), &ProjectId::new("pid"));
317+
assert_eq!(dsn.path(), "/pathone/pathtwo/");
309318
}
310319

311320
#[test]

sentry-types/src/project_id.rs

Lines changed: 21 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::convert::TryFrom;
21
use std::fmt;
32
use std::str::FromStr;
43

@@ -8,88 +7,45 @@ use thiserror::Error;
87
/// Raised if a project ID cannot be parsed from a string.
98
#[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord)]
109
pub enum ParseProjectIdError {
11-
/// Raised if the value is not an integer in the supported range.
12-
#[error("invalid value for project id")]
13-
InvalidValue,
1410
/// Raised if an empty value is parsed.
1511
#[error("empty or missing project id")]
1612
EmptyValue,
1713
}
1814

1915
/// Represents a project ID.
20-
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
21-
pub struct ProjectId(u64);
16+
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
17+
pub struct ProjectId(String);
2218

2319
impl ProjectId {
24-
/// Creates a new project ID from its numeric value.
20+
/// Creates a new project ID from its string representation.
21+
/// This assumes that the string is already well-formed and URL
22+
/// encoded/decoded.
2523
#[inline]
26-
pub fn new(id: u64) -> Self {
27-
Self(id)
24+
pub fn new(id: &str) -> Self {
25+
Self(id.to_string())
2826
}
2927

30-
/// Returns the numeric value of this project id.
28+
/// Returns the string representation of the project ID.
3129
#[inline]
32-
pub fn value(self) -> u64 {
33-
self.0
30+
pub fn value(&self) -> &str {
31+
self.0.as_ref()
3432
}
3533
}
3634

3735
impl fmt::Display for ProjectId {
3836
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39-
write!(f, "{}", self.value())
37+
write!(f, "{}", self.0)
4038
}
4139
}
4240

43-
macro_rules! impl_from {
44-
($ty:ty) => {
45-
impl From<$ty> for ProjectId {
46-
#[inline]
47-
fn from(val: $ty) -> Self {
48-
Self::new(val as u64)
49-
}
50-
}
51-
};
52-
}
53-
54-
impl_from!(u8);
55-
impl_from!(u16);
56-
impl_from!(u32);
57-
impl_from!(u64);
58-
59-
macro_rules! impl_try_from {
60-
($ty:ty) => {
61-
impl TryFrom<$ty> for ProjectId {
62-
type Error = ParseProjectIdError;
63-
64-
#[inline]
65-
fn try_from(val: $ty) -> Result<Self, Self::Error> {
66-
match u64::try_from(val) {
67-
Ok(id) => Ok(Self::new(id)),
68-
Err(_) => Err(ParseProjectIdError::InvalidValue),
69-
}
70-
}
71-
}
72-
};
73-
}
74-
75-
impl_try_from!(usize);
76-
impl_try_from!(i8);
77-
impl_try_from!(i16);
78-
impl_try_from!(i32);
79-
impl_try_from!(i64);
80-
8141
impl FromStr for ProjectId {
8242
type Err = ParseProjectIdError;
8343

8444
fn from_str(s: &str) -> Result<ProjectId, ParseProjectIdError> {
8545
if s.is_empty() {
8646
return Err(ParseProjectIdError::EmptyValue);
8747
}
88-
89-
match s.parse::<u64>() {
90-
Ok(val) => Ok(ProjectId::new(val)),
91-
Err(_) => Err(ParseProjectIdError::InvalidValue),
92-
}
48+
Ok(ProjectId::new(s))
9349
}
9450
}
9551

@@ -100,21 +56,21 @@ mod test {
10056
#[test]
10157
fn test_basic_api() {
10258
let id: ProjectId = "42".parse().unwrap();
103-
assert_eq!(id, ProjectId::new(42));
104-
assert_eq!(
105-
"42xxx".parse::<ProjectId>(),
106-
Err(ParseProjectIdError::InvalidValue)
107-
);
59+
assert_eq!(id, ProjectId::new("42"));
60+
assert_eq!("42xxx".parse::<ProjectId>().unwrap().value(), "42xxx");
10861
assert_eq!(
10962
"".parse::<ProjectId>(),
11063
Err(ParseProjectIdError::EmptyValue)
11164
);
112-
assert_eq!(ProjectId::new(42).to_string(), "42");
65+
assert_eq!(ProjectId::new("42").to_string(), "42");
11366

114-
assert_eq!(serde_json::to_string(&ProjectId::new(42)).unwrap(), "42");
11567
assert_eq!(
116-
serde_json::from_str::<ProjectId>("42").unwrap(),
117-
ProjectId::new(42)
68+
serde_json::to_string(&ProjectId::new("42")).unwrap(),
69+
"\"42\""
70+
);
71+
assert_eq!(
72+
serde_json::from_str::<ProjectId>("\"42\"").unwrap(),
73+
ProjectId::new("42")
11874
);
11975
}
12076
}

sentry/tests/test_client.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ use std::sync::Arc;
55

66
#[test]
77
fn test_into_client() {
8-
let c: sentry::Client = sentry::Client::from_config("https://[email protected]/42");
8+
let c: sentry::Client = sentry::Client::from_config("https://[email protected]/42%21");
99
{
1010
let dsn = c.dsn().unwrap();
1111
assert_eq!(dsn.public_key(), "public");
1212
assert_eq!(dsn.host(), "example.com");
1313
assert_eq!(dsn.scheme(), sentry::types::Scheme::Https);
14-
assert_eq!(dsn.project_id().value(), 42);
14+
assert_eq!(dsn.project_id().value(), "42%21");
1515
}
1616

1717
let c: sentry::Client = sentry::Client::from_config((
18-
"https://[email protected]/42",
18+
"https://[email protected]/42%21",
1919
sentry::ClientOptions {
2020
release: Some("[email protected]".into()),
2121
..Default::default()
@@ -26,7 +26,7 @@ fn test_into_client() {
2626
assert_eq!(dsn.public_key(), "public");
2727
assert_eq!(dsn.host(), "example.com");
2828
assert_eq!(dsn.scheme(), sentry::types::Scheme::Https);
29-
assert_eq!(dsn.project_id().value(), 42);
29+
assert_eq!(dsn.project_id().value(), "42%21");
3030
assert_eq!(&c.options().release.as_ref().unwrap(), &"[email protected]");
3131
}
3232

0 commit comments

Comments
 (0)