Skip to main content

rama_ua/layer/emulate/
provider.rs

1use rama_core::extensions::{Extension, Extensions};
2use rama_utils::str::arcstr::ArcStr;
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5
6use crate::{
7    PlatformKind, UserAgentKind,
8    profile::{UserAgentDatabase, UserAgentProfile, UserAgentRuntimeProfile},
9};
10
11#[derive(Debug, Clone, Serialize, Deserialize, Extension)]
12#[extension(tags(ua))]
13/// Extra information about the selected user agent profile,
14/// which isn't already injected. E.g. http and tls information
15/// is already injected separately.
16pub struct SelectedUserAgentProfile {
17    /// The user agent header of the selected profile.
18    pub user_agent_header: Option<ArcStr>,
19
20    /// The kind of [`crate::UserAgent`]
21    pub ua_kind: UserAgentKind,
22    /// The version of the [`crate::UserAgent`]
23    pub ua_version: Option<usize>,
24    /// The platform the [`crate::UserAgent`] is running on.
25    pub platform: Option<PlatformKind>,
26
27    /// Runtime (meta) info about the UA profile of the [`crate::UserAgent`].
28    pub runtime: Option<Arc<UserAgentRuntimeProfile>>,
29}
30
31impl From<&UserAgentProfile> for SelectedUserAgentProfile {
32    fn from(profile: &UserAgentProfile) -> Self {
33        Self {
34            user_agent_header: profile.ua_str().map(Into::into),
35            ua_kind: profile.ua_kind,
36            ua_version: profile.ua_version,
37            platform: profile.platform,
38            runtime: profile.runtime.clone(),
39        }
40    }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Extension)]
44#[extension(tags(ua))]
45/// Fallback strategy that can be injected into the context
46/// to customise what a provider can be requested to do
47/// in case the preconditions for UA selection were not fulfilled.
48///
49/// It is advised only fallback for pre-conditions and not
50/// post-selection failure as the latter would be rather confusing.
51///
52/// For example if you request a Chromium profile you do not expect a Firefox one.
53/// However if you do not give any filters it is fair to assume a random profile is desired,
54/// given those all satisfy the abscence of filters.
55pub enum UserAgentSelectFallback {
56    #[default]
57    /// Abort the request if no profile is found.
58    Abort,
59    /// Select a random profile if no profile is found.
60    Random,
61}
62
63/// A trait for providing user agent profiles for emulation.
64///
65/// This trait is used to select a user agent profile based on the current context.
66/// It's a core component of the user agent emulation system, allowing different
67/// strategies for selecting which user agent profile to use for a request.
68///
69/// Rama provides several built-in implementations:
70/// - `()`: Always returns `None`, effectively disabling user agent emulation
71/// - [`UserAgentProfile`]: Always returns the same profile
72/// - [`UserAgentDatabase`]: Selects a profile based on the [`UserAgent`] in the context,
73///   or falls back to a random profile if configured with [`UserAgentSelectFallback::Random`]
74/// - [`Option<P>`]: Delegates to the inner provider if `Some`, otherwise returns `None`
75///
76/// This trait is typically used by [`UserAgentEmulateService`] to select an appropriate
77/// user agent profile for HTTP request emulation.
78///
79/// [`UserAgentProfile`]: crate::profile::UserAgentProfile
80/// [`UserAgentDatabase`]: crate::profile::UserAgentDatabase
81/// [`UserAgent`]: crate::UserAgent
82/// [`UserAgentSelectFallback::Random`]: UserAgentSelectFallback::Random
83/// [`UserAgentEmulateService`]: crate::layer::emulate::UserAgentEmulateService
84pub trait UserAgentProvider: Send + Sync + 'static {
85    /// Selects a user agent profile based on the current context.
86    fn select_user_agent_profile(&self, extensions: &Extensions) -> Option<&UserAgentProfile>;
87}
88
89impl UserAgentProvider for () {
90    #[inline]
91    fn select_user_agent_profile(&self, _extensions: &Extensions) -> Option<&UserAgentProfile> {
92        None
93    }
94}
95
96impl UserAgentProvider for UserAgentProfile {
97    #[inline]
98    fn select_user_agent_profile(&self, _extensions: &Extensions) -> Option<&UserAgentProfile> {
99        Some(self)
100    }
101}
102
103impl UserAgentProvider for UserAgentDatabase {
104    #[inline]
105    fn select_user_agent_profile(&self, extensions: &Extensions) -> Option<&UserAgentProfile> {
106        match (extensions.get_ref(), extensions.get_ref()) {
107            (Some(agent), _) => self.get(agent),
108            (None, Some(UserAgentSelectFallback::Random)) => self.rnd(),
109            (None, None | Some(UserAgentSelectFallback::Abort)) => None,
110        }
111    }
112}
113
114impl<P> UserAgentProvider for Option<P>
115where
116    P: UserAgentProvider,
117{
118    #[inline]
119    fn select_user_agent_profile(&self, extensions: &Extensions) -> Option<&UserAgentProfile> {
120        self.as_ref()
121            .and_then(|p| p.select_user_agent_profile(extensions))
122    }
123}
124
125impl<P> UserAgentProvider for Arc<P>
126where
127    P: UserAgentProvider,
128{
129    #[inline]
130    fn select_user_agent_profile(&self, extensions: &Extensions) -> Option<&UserAgentProfile> {
131        self.as_ref().select_user_agent_profile(extensions)
132    }
133}
134
135impl<P> UserAgentProvider for Box<P>
136where
137    P: UserAgentProvider,
138{
139    #[inline]
140    fn select_user_agent_profile(&self, extensions: &Extensions) -> Option<&UserAgentProfile> {
141        self.as_ref().select_user_agent_profile(extensions)
142    }
143}
144
145macro_rules! impl_user_agent_provider_either {
146    ($id:ident, $($param:ident),+ $(,)?) => {
147        impl< $($param),+> UserAgentProvider for ::rama_core::combinators::$id<$($param),+>
148        where
149            $(
150                $param: UserAgentProvider,
151            )+
152        {
153            fn select_user_agent_profile(
154                &self,
155                extensions: &Extensions,
156            ) -> Option<&UserAgentProfile> {
157                match self {
158                    $(
159                        ::rama_core::combinators::$id::$param(s) => s.select_user_agent_profile(extensions),
160                    )+
161                }
162            }
163        }
164    };
165}
166
167::rama_core::combinators::impl_either!(impl_user_agent_provider_either);