1use std::time::Duration;
4
5use reqwest::header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue};
6
7use crate::error::TesseraError;
8
9pub const DEFAULT_BASE_URL: &str = "https://tesseralytics.dev";
11
12pub const API_KEY_ENV_VAR: &str = "TESSERA_API_KEY";
14
15pub const USER_AGENT: &str = concat!("tessera-rust/", env!("CARGO_PKG_VERSION"));
17
18pub(crate) const RETRYABLE: &[u16] = &[429, 500, 502, 503, 504];
20
21pub fn resolve_api_key(api_key: Option<&str>) -> Result<String, TesseraError> {
27 api_key
28 .map(str::to_string)
29 .or_else(|| std::env::var(API_KEY_ENV_VAR).ok())
30 .filter(|key| !key.is_empty())
31 .ok_or_else(|| {
32 TesseraError::Configuration(
33 "No API key provided. Pass api_key=... or set the TESSERA_API_KEY environment variable. Get a free key at https://tesseralytics.dev.".to_string(),
34 )
35 })
36}
37
38#[must_use]
44pub fn backoff_delay(attempt: u32, retry_after: Option<f64>) -> Duration {
45 if let Some(seconds) = retry_after.filter(|s| *s >= 0.0) {
46 return Duration::from_secs_f64(seconds);
47 }
48 let base = 0.5 * 2f64.powi(i32::try_from(attempt).unwrap_or(63));
49 Duration::from_secs_f64(fastrand::f64() * base)
50}
51
52#[derive(Debug, Clone)]
54pub struct ClientConfig {
55 pub api_key: String,
57 pub base_url: String,
59 pub timeout: Duration,
61 pub max_retries: u32,
63 pub user_agent: String,
65}
66
67impl ClientConfig {
68 pub fn new(api_key: Option<&str>) -> Result<Self, TesseraError> {
74 Ok(Self {
75 api_key: resolve_api_key(api_key)?,
76 base_url: DEFAULT_BASE_URL.to_string(),
77 timeout: Duration::from_secs(30),
78 max_retries: 3,
79 user_agent: USER_AGENT.to_string(),
80 })
81 }
82
83 #[must_use]
85 pub fn auth_headers(&self) -> HeaderMap {
86 let mut headers = HeaderMap::new();
87 if let Ok(value) = HeaderValue::from_str(&format!("Bearer {}", self.api_key)) {
88 headers.insert(AUTHORIZATION, value);
89 }
90 if let Ok(value) = HeaderValue::from_str(&self.user_agent) {
91 headers.insert(reqwest::header::USER_AGENT, value);
92 }
93 headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
94 headers
95 }
96}