rama_ua/emulate/
provider.rs

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