use std::sync::OnceLock;
pub(crate) fn user_agent() -> &'static str {
static UA: OnceLock<String> = OnceLock::new();
UA.get_or_init(|| {
let os = sysinfo::System::long_os_version()
.or_else(sysinfo::System::name)
.unwrap_or_else(|| std::env::consts::OS.to_owned());
format!(
"bestool/{} ({os}; {})",
env!("CARGO_PKG_VERSION"),
sysinfo::System::cpu_arch(),
)
})
}
pub(crate) fn client_builder() -> reqwest::ClientBuilder {
reqwest::Client::builder()
.user_agent(user_agent())
.tls_sslkeylogfile(true)
}
pub(crate) fn client() -> reqwest::Client {
client_builder()
.build()
.expect("failed to build bestool HTTP client")
}
pub(crate) const DAEMON_BASES: [&str; 2] = ["http://[::1]:8271", "http://127.0.0.1:8271"];
pub(crate) async fn daemon_get(
client: &reqwest::Client,
path: &str,
configure: impl Fn(reqwest::RequestBuilder) -> reqwest::RequestBuilder,
) -> reqwest::Result<reqwest::Response> {
let mut last_err = None;
for base in DAEMON_BASES {
let request = configure(client.get(format!("{base}{path}")));
match request.send().await {
Ok(response) => return Ok(response),
Err(err) => last_err = Some(err),
}
}
Err(last_err.expect("DAEMON_BASES is never empty"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn user_agent_has_product_and_os_comment() {
let ua = user_agent();
assert!(ua.starts_with("bestool/"), "unexpected user-agent: {ua}");
assert!(ua.contains('('), "expected OS comment in: {ua}");
assert!(ua.ends_with(')'), "expected OS comment in: {ua}");
assert!(
ua.contains(sysinfo::System::cpu_arch().as_str()),
"expected arch in: {ua}"
);
}
}