Skip to content

Commit 5e95161

Browse files
flubdjc
authored andcommitted
Make token cheap to clone
This puts the token inside an Arc'ed inner, making clones cheaper. Because ServiceAccounts clone the token every time they give it out this adds up when using enough tokes per minute. The tests were written before the changes to token to ensure their (de)serialising behaviour stayed the same.
1 parent d536ea6 commit 5e95161

File tree

2 files changed

+67
-13
lines changed

2 files changed

+67
-13
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ hyper-rustls = { version = "0.23.0", default-features = false, features = ["nati
2222
log = "0.4"
2323
rustls = "0.20.2"
2424
rustls-pemfile = "0.2.1"
25-
serde = {version = "1.0", features = ["derive"]}
25+
serde = {version = "1.0", features = ["derive", "rc"]}
2626
serde_json = "1.0"
2727
tokio = { version = "1.1", features = ["fs"] }
2828
url = "2"

src/types.rs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,56 @@
11
use std::fmt;
2+
use std::sync::Arc;
23

34
use serde::Deserializer;
45
use serde::{Deserialize, Serialize};
56
use time::{Duration, OffsetDateTime};
67

78
/// Represents an access token. All access tokens are Bearer tokens.
89
///
9-
/// Tokens cannot be cached.
10+
/// Tokens should not be cached, the [`AuthenticationManager`] handles the correct caching
11+
/// already. Tokens are cheap to clone.
1012
///
1113
/// The token does not implement [`Display`] to avoid accidentally printing the token in log
1214
/// files, likewise [`Debug`] does not expose the token value itself which is only available
1315
/// using the [Token::`as_str`] method.
1416
///
17+
/// [`AuthenticationManager`]: crate::AuthenticationManager
1518
/// [`Display`]: fmt::Display
1619
/// [`Debug`]: fmt::Debug
1720
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
1821
pub struct Token {
19-
access_token: String,
20-
#[serde(
21-
default,
22-
deserialize_with = "deserialize_time",
23-
rename(deserialize = "expires_in")
24-
)]
25-
expires_at: Option<OffsetDateTime>,
22+
#[serde(flatten)]
23+
inner: Arc<InnerToken>,
2624
}
2725

2826
impl fmt::Debug for Token {
2927
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3028
f.debug_struct("Token")
3129
.field("access_token", &"****")
32-
.field("expires_at", &self.expires_at)
30+
.field("expires_at", &self.inner.expires_at)
3331
.finish()
3432
}
3533
}
3634

35+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
36+
struct InnerToken {
37+
access_token: String,
38+
#[serde(
39+
default,
40+
deserialize_with = "deserialize_time",
41+
rename(deserialize = "expires_in")
42+
)]
43+
expires_at: Option<OffsetDateTime>,
44+
}
45+
3746
impl Token {
3847
/// Define if the token has has_expired
3948
///
4049
/// This takes an additional 30s margin to ensure the token can still be reasonably used
4150
/// instead of expiring right after having checked.
4251
pub fn has_expired(&self) -> bool {
43-
self.expires_at
52+
self.inner
53+
.expires_at
4454
.map(|expiration_time| {
4555
expiration_time - Duration::seconds(30) <= OffsetDateTime::now_utc()
4656
})
@@ -49,12 +59,12 @@ impl Token {
4959

5060
/// Get str representation of the token.
5161
pub fn as_str(&self) -> &str {
52-
&self.access_token
62+
&self.inner.access_token
5363
}
5464

5565
/// Get expiry of token, if available
5666
pub fn expires_at(&self) -> Option<OffsetDateTime> {
57-
self.expires_at
67+
self.inner.expires_at
5868
}
5969
}
6070

@@ -70,3 +80,47 @@ where
7080

7181
pub(crate) type HyperClient =
7282
hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use super::*;
87+
88+
#[test]
89+
fn test_serialise() {
90+
let token = Token {
91+
inner: Arc::new(InnerToken {
92+
access_token: "abc123".to_string(),
93+
expires_at: Some(OffsetDateTime::from_unix_timestamp(123).unwrap()),
94+
}),
95+
};
96+
let s = serde_json::to_string(&token).unwrap();
97+
98+
assert_eq!(
99+
s,
100+
r#"{"access_token":"abc123","expires_at":[1970,1,0,2,3,0,0,0,0]}"#
101+
);
102+
}
103+
104+
#[test]
105+
fn test_deserialise_with_time() {
106+
let s = r#"{"access_token":"abc123","expires_in":100}"#;
107+
let token: Token = serde_json::from_str(s).unwrap();
108+
let expires = OffsetDateTime::now_utc() + Duration::seconds(100);
109+
110+
assert_eq!(token.as_str(), "abc123");
111+
112+
// Testing time is always racy, give it 1s leeway.
113+
let expires_at = token.expires_at().unwrap();
114+
assert!(expires_at < expires + Duration::seconds(1));
115+
assert!(expires_at > expires - Duration::seconds(1));
116+
}
117+
118+
#[test]
119+
fn test_deserialise_no_time() {
120+
let s = r#"{"access_token":"abc123"}"#;
121+
let token: Token = serde_json::from_str(s).unwrap();
122+
123+
assert_eq!(token.as_str(), "abc123");
124+
assert!(token.expires_at().is_none());
125+
}
126+
}

0 commit comments

Comments
 (0)