rama-ua 0.3.0

user-agent (UA) support for rama
Documentation
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorExt as _};
use rama_core::extensions::Extension;
use rama_http::headers::ClientHint;
use rama_utils::macros::match_ignore_ascii_case_str;
use std::sync::Arc;
use std::{fmt, ops::Deref, str::FromStr};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Extension)]
#[extension(tags(ua))]
#[non_exhaustive]
/// Runtime hint to request a user agent to be preserved,
/// useful for systems that modify requests based on the context and request,
/// but still wish to support preserving the original header user-agent.
///
/// Used by [`UserAgentEmulateService`].
///
/// [`UserAgentEmulateService`]: crate::layer::emulate::UserAgentEmulateService
pub struct PreserveHeaderUserAgent;

impl PreserveHeaderUserAgent {
    #[inline]
    /// Create a new [`PreserveHeaderUserAgent`].
    #[must_use]
    pub fn new() -> Self {
        Default::default()
    }
}

/// ClientHints requested for the (http) Request.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Extension)]
#[extension(tags(ua))]
pub struct RequestClientHints(pub Arc<[ClientHint]>);

impl AsRef<[ClientHint]> for RequestClientHints {
    fn as_ref(&self) -> &[ClientHint] {
        self.0.deref()
    }
}

impl Deref for RequestClientHints {
    type Target = [ClientHint];

    fn deref(&self) -> &Self::Target {
        self.0.deref()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Extension)]
#[extension(tags(ua))]
/// The initiator of the (http) Request.
///
/// It is used to determine the request initiator for the to be emulated http request,
/// and can either be attempted to be detected or manually injected into the [`Extensions`],
/// or implicitly via a layer service to extract that info from a custom http header.
///
/// It is used by [`UserAgentEmulateHttpRequestModifier`] to determine the request initiator
/// for the to be emulated http request, and indicates the preferred headers to use from
/// the [`HttpHeadersProfile`] to emulate the request.
///
/// [`Extensions`]: rama_core::extensions::Extensions
/// [`UserAgentEmulateHttpRequestModifier`]: crate::layer::emulate::UserAgentEmulateHttpRequestModifier
/// [`HttpHeadersProfile`]: crate::profile::HttpHeadersProfile
pub enum RequestInitiator {
    /// normal navigate, expexted by all [`super::UserAgentProfile`] to be implemented
    Navigate,
    /// form action, considered optional (e.g. native non-browser user-agents typically don't support this)
    Form,
    /// XML Http Request (Original Ajax tech in browsers), to fetch content from (Js) scripts,
    /// considered optional (e.g. a very modern non-browser user-agent might support only the more
    /// modern Fetch API without backwards compatibility for the older XMLHttpRequest API)
    Xhr,
    /// Fetch API ("Modern" async-able approach to fetch content from (Js) scripts),
    /// optional and for example not supported by user-agents that are only capable of
    /// handling regular navigate requests or do not support more than one request type.
    Fetch,
    /// WebSocket Upgrade Request
    Ws,
}

impl RequestInitiator {
    /// Get the string representation of the request initiator.
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Navigate => "navigate",
            Self::Form => "form",
            Self::Xhr => "xhr",
            Self::Fetch => "fetch",
            Self::Ws => "ws",
        }
    }
}

impl fmt::Display for RequestInitiator {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

use rama_utils::macros::serde_str::impl_serde_str;

impl_serde_str!(as_str RequestInitiator);

impl FromStr for RequestInitiator {
    type Err = BoxError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match_ignore_ascii_case_str! {
            match (s) {
                "navigate" => Ok(Self::Navigate),
                "form" => Ok(Self::Form),
                "xhr" => Ok(Self::Xhr),
                "fetch" => Ok(Self::Fetch),
                _ => Err(BoxError::from_static_str("invalid request initiator").context_str_field("str", s)),
            }
        }
    }
}