statsig_rust/evaluation/user_agent_parsing/
ua_parser.rs

1use std::sync::Arc;
2
3use crate::evaluation::dynamic_string::DynamicString;
4use crate::evaluation::user_agent_parsing::ParsedUserAgentValue;
5use crate::interned_string::InternedString;
6use crate::user::StatsigUserInternal;
7use crate::{log_w, unwrap_or_return, DynamicValue, StatsigOptions, StatsigUser};
8
9use super::experimental_ua_parser::ExperimentalUserAgentParser;
10use super::third_party_ua_parser::ThirdPartyUserAgentParser;
11
12lazy_static::lazy_static! {
13    static ref USER_AGENT_STRING: Option<DynamicString> = Some(DynamicString::from("userAgent".to_string()));
14}
15
16const TAG: &str = "UserAgentParser";
17const UNINITIALIZED_REASON: &str = "UAParserNotLoaded";
18
19pub struct UserAgentParser;
20
21fn get_experimental_ua_value(key: &str, ua: &str) -> Option<InternedString> {
22    ExperimentalUserAgentParser::get_value_from_user_agent(key, ua)
23        .and_then(|v| v.string_value.map(|s| s.value))
24}
25
26fn get_third_party_ua_value(key: &str, ua: &str) -> Option<InternedString> {
27    ThirdPartyUserAgentParser::get_value_from_user_agent(key, ua)
28        .ok()
29        .flatten()
30        .and_then(|dv| dv.string_value.map(|s| s.value))
31}
32
33impl UserAgentParser {
34    pub fn get_value_from_user_agent(
35        user: &StatsigUserInternal,
36        field: &Option<DynamicString>,
37        override_reason: &mut Option<&str>,
38        use_experimental_ua_parser: bool,
39    ) -> Option<DynamicValue> {
40        let field_lowered = match field {
41            Some(f) => f.lowercased_value.as_str(),
42            _ => return None,
43        };
44
45        let user_agent = match user.get_user_value(&USER_AGENT_STRING) {
46            Some(v) => match &v.string_value {
47                Some(s) => &s.value,
48                _ => return None,
49            },
50            None => return None,
51        };
52
53        if user_agent.len() > 1000 {
54            return None;
55        }
56
57        if use_experimental_ua_parser {
58            ExperimentalUserAgentParser::get_value_from_user_agent(field_lowered, user_agent)
59        } else {
60            let result =
61                ThirdPartyUserAgentParser::get_value_from_user_agent(field_lowered, user_agent);
62
63            match result {
64                Ok(v) => v,
65                Err(_) => {
66                    *override_reason = Some(UNINITIALIZED_REASON);
67                    log_w!(TAG, "Failed to load UA Parser. Check StatsigOptions.disable_user_agent_parsing and or wait_for_user_agent_init");
68                    None
69                }
70            }
71        }
72    }
73
74    pub fn load_parser() {
75        ThirdPartyUserAgentParser::load_parser();
76    }
77
78    pub fn get_parsed_user_agent_value_for_user(
79        user: &StatsigUser,
80        options: &Arc<StatsigOptions>,
81    ) -> Option<ParsedUserAgentValue> {
82        let user_agent_str = unwrap_or_return!(user.get_user_agent(), None);
83        match options.__experimental_ua_parsing_enabled {
84            Some(true) => Some(ParsedUserAgentValue {
85                os_name: get_experimental_ua_value("os_name", user_agent_str),
86                os_version: get_experimental_ua_value("os_version", user_agent_str),
87                browser_name: get_experimental_ua_value("browser_name", user_agent_str),
88                browser_version: get_experimental_ua_value("browser_version", user_agent_str),
89            }),
90            _ => Some(ParsedUserAgentValue {
91                os_name: get_third_party_ua_value("os_name", user_agent_str),
92                os_version: get_third_party_ua_value("os_version", user_agent_str),
93                browser_name: get_third_party_ua_value("browser_name", user_agent_str),
94                browser_version: get_third_party_ua_value("browser_version", user_agent_str),
95            }),
96        }
97    }
98}