Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

//! User agent calculation and injection for HTTP calls.

use crate::extract::FromContext;
use std::cell::RefCell;
use std::convert::Infallible;
use std::rc::Rc;

const PDK_AGENT: &str = "PDK-HttpClient";
const PDK_VERSION: &str = env!("CARGO_PKG_VERSION");

thread_local! {
    /// The user agent value is the same for all the filters. We can safely cache it once read to avoid
    /// re-computation.
    static USER_AGENT: RefCell<Option<Rc<UserAgent>>> = const { RefCell::new(None) };
}

impl<C> FromContext<C> for Rc<UserAgent> {
    type Error = Infallible;

    fn from_context(_context: &C) -> Result<Self, Self::Error> {
        let agent = USER_AGENT
            .with(|cell| cell.borrow().clone())
            .unwrap_or_default();

        Ok(Rc::clone(&agent))
    }
}

/// Set the user agent globally with the given user agent value.
/// All HttpClients constructed after setting the user agent will use this value.
pub fn set_user_agent(user_agent: UserAgent) {
    USER_AGENT.with(|cell| cell.replace(Some(Rc::new(user_agent))));
}

/// The user agent value to be used in HTTP calls.
pub struct UserAgent {
    value: String,
}

impl Default for UserAgent {
    fn default() -> Self {
        UserAgent::new(PDK_AGENT, PDK_VERSION)
    }
}

impl UserAgent {
    /// Create a new user agent with the format {key}/{version}.
    pub fn new(key: &str, version: &str) -> Self {
        UserAgent {
            value: format!("{key}/{version}"),
        }
    }

    /// The value that will be used for the User-Agent header.
    pub fn value(&self) -> &str {
        self.value.as_str()
    }
}

#[cfg(test)]
mod test {
    use crate::user_agent::UserAgent;

    #[test]
    fn default() {
        assert_eq!(
            UserAgent::default().value(),
            &format!("PDK-HttpClient/{}", env!("CARGO_PKG_VERSION"))
        );
    }
}