statsig_rust/evaluation/user_agent_parsing/
third_party_ua_parser.rs

1use crate::{dyn_value, log_d, log_e, DynamicValue};
2use parking_lot::RwLock;
3use std::borrow::Cow;
4use std::sync::Arc;
5use uaparser::{Parser, UserAgentParser as ExtUserAgentParser};
6
7lazy_static::lazy_static! {
8    static ref PARSER: Arc<RwLock<Option<ExtUserAgentParser>>> = Arc::from(RwLock::from(None));
9}
10
11const TAG: &str = "ThirdPartyUserAgentParser";
12
13pub struct ThirdPartyUserAgentParser;
14
15impl ThirdPartyUserAgentParser {
16    pub fn get_value_from_user_agent(
17        field: &str,
18        user_agent: &str,
19    ) -> Result<Option<DynamicValue>, &'static str> {
20        let lock = PARSER
21            .try_read_for(std::time::Duration::from_secs(5))
22            .ok_or("lock_failure")?;
23        let parser = lock.as_ref().ok_or("parser_not_loaded")?;
24
25        fn get_json_version(
26            major: Option<Cow<str>>,
27            minor: Option<Cow<str>>,
28            patch: Option<Cow<str>>,
29        ) -> String {
30            let mut result = String::new();
31            result += &major.unwrap_or(Cow::Borrowed("0"));
32            result += ".";
33            result += &minor.unwrap_or(Cow::Borrowed("0"));
34            result += ".";
35            result += &patch.unwrap_or(Cow::Borrowed("0"));
36            result
37        }
38
39        let result = match field {
40            "os_name" | "osname" => {
41                let os = parser.parse_os(user_agent);
42                os.family.to_string()
43            }
44            "os_version" | "osversion" => {
45                let os = parser.parse_os(user_agent);
46                get_json_version(os.major, os.minor, os.patch)
47            }
48            "browser_name" | "browsername" => {
49                let user_agent = parser.parse_user_agent(user_agent);
50                user_agent.family.to_string()
51            }
52            "browser_version" | "browserversion" => {
53                let user_agent = parser.parse_user_agent(user_agent);
54                get_json_version(user_agent.major, user_agent.minor, user_agent.patch)
55            }
56            _ => return Ok(None),
57        };
58
59        Ok(Some(dyn_value!(result)))
60    }
61
62    pub fn load_parser() {
63        match PARSER.try_read_for(std::time::Duration::from_secs(5)) {
64            Some(lock) => {
65                if lock.is_some() {
66                    log_d!(TAG, "Parser already loaded");
67                    return;
68                }
69            }
70            None => {
71                log_e!(
72                    TAG,
73                    "Failed to acquire read lock on parser: Failed to lock PARSER"
74                );
75                return;
76            }
77        }
78
79        log_d!(TAG, "Loading User Agent Parser...");
80
81        let bytes = include_bytes!("../../../resources/ua_parser_regex_lite.yaml");
82        let parser = match ExtUserAgentParser::from_bytes(bytes) {
83            Ok(parser) => parser,
84            Err(e) => {
85                log_e!(TAG, "Failed to load parser: {}", e);
86                return;
87            }
88        };
89
90        match PARSER.try_write_for(std::time::Duration::from_secs(5)) {
91            Some(mut lock) => {
92                *lock = Some(parser);
93                log_d!(TAG, "User Agent Parser Successfully Loaded");
94            }
95            None => {
96                log_e!(
97                    TAG,
98                    "Failed to acquire write lock on parser: Failed to lock PARSER"
99                );
100            }
101        }
102    }
103}