use std::{
fs,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use serde::Deserialize;
use super::Client;
pub const TOKEN_CACHE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/last_token.txt");
#[derive(Deserialize)]
pub struct TestConfig {
pub consumer_key: String,
pub consumer_secret: String,
pub passkey: String,
pub callback_url: String,
pub phone_number: u64,
}
impl TestConfig {
pub fn load() -> Self {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/config.toml");
let contents = std::fs::read_to_string(path)
.expect("copy config.toml.example to config.toml and add sandbox credentials");
toml::from_str(&contents).expect("config.toml is malformed")
}
}
pub fn read_token_cache() -> Option<(String, SystemTime)> {
let contents = fs::read_to_string(TOKEN_CACHE_PATH).ok()?;
let mut parts = contents.splitn(2, ':');
let access_token = parts.next()?.trim().to_string();
if access_token.is_empty() {
return None;
}
let expires_at_secs: u64 = parts.next()?.trim().parse().ok()?;
let expires_at = UNIX_EPOCH.checked_add(Duration::from_secs(expires_at_secs))?;
if expires_at <= SystemTime::now() {
return None;
}
Some((access_token, expires_at))
}
pub fn write_token_cache(access_token: &str, expires_in: u64) {
let expires_at = SystemTime::now()
.checked_add(Duration::from_secs(expires_in))
.expect("expires_in should fit in SystemTime");
let expires_at_secs = expires_at
.duration_since(UNIX_EPOCH)
.expect("expiry should be after UNIX_EPOCH")
.as_secs();
fs::write(
TOKEN_CACHE_PATH,
format!("{access_token}:{expires_at_secs}"),
)
.expect("failed to write token cache");
}
pub fn assert_valid_access_token(access_token: &str) {
assert!(!access_token.is_empty());
}
pub fn assert_valid_expires_in(expires_in: u64) {
assert!(expires_in > 0);
assert!(expires_in <= 3600);
}
pub async fn get_access_token() -> String {
if let Some((cached_token, expires_at)) = read_token_cache() {
assert_valid_access_token(&cached_token);
let remaining = expires_at
.duration_since(SystemTime::now())
.expect("cached token should not be expired");
assert_valid_expires_in(remaining.as_secs());
cached_token
} else {
let test_config = TestConfig::load();
let client =
Client::with_credentials(test_config.consumer_key, test_config.consumer_secret);
let response = client
.generate_access_token()
.await
.expect("failed to generate sandbox access token");
let expires_in: u64 = response
.expires_in
.parse()
.expect("expires_in should be a positive integer");
write_token_cache(&response.access_token, expires_in);
response.access_token
}
}