mod http_client {
use once_type_map::OnceTypeMap;
pub struct Client {
base_url: String,
extensions: OnceTypeMap,
}
impl Client {
pub fn new(base_url: &str) -> Self {
Client {
base_url: base_url.to_string(),
extensions: OnceTypeMap::new(),
}
}
pub fn base_url(&self) -> &str {
&self.base_url
}
pub fn extensions(&self) -> &OnceTypeMap {
&self.extensions
}
}
}
mod auth {
use crate::http_client::Client;
struct Token(String);
pub trait ClientAuthExt {
fn set_token(&self, token: &str) -> bool;
fn token(&self) -> Option<&str>;
}
impl ClientAuthExt for Client {
fn set_token(&self, token: &str) -> bool {
self.extensions().insert(Token(token.to_string())).is_ok()
}
fn token(&self) -> Option<&str> {
self.extensions().get::<Token>().map(|t| t.0.as_str())
}
}
}
mod stats {
use crate::http_client::Client;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Default)]
pub struct Stats {
requests: AtomicU64,
}
impl Stats {
pub fn record_request(&self) {
self.requests.fetch_add(1, Ordering::Relaxed);
}
pub fn requests(&self) -> u64 {
self.requests.load(Ordering::Relaxed)
}
}
pub trait ClientStatsExt {
fn stats(&self) -> &Stats;
}
impl ClientStatsExt for Client {
fn stats(&self) -> &Stats {
self.extensions().get_or_init(Stats::default)
}
}
}
use auth::ClientAuthExt;
use http_client::Client;
use stats::ClientStatsExt;
fn send(client: &Client, path: &str) {
client.stats().record_request();
let signed_in = if client.token().is_some() {
"with token"
} else {
"anonymous"
};
println!("GET {}{path} ({signed_in})", client.base_url());
}
fn main() {
let client = Client::new("https://api.example.com");
send(&client, "/status");
assert!(client.set_token("s3cret"));
assert!(!client.set_token("other"), "the token is set only once");
std::thread::scope(|s| {
s.spawn(|| send(&client, "/users"));
s.spawn(|| send(&client, "/orders"));
});
println!("{} requests sent", client.stats().requests());
assert_eq!(client.stats().requests(), 3);
assert_eq!(client.token(), Some("s3cret"));
}