statsig_rust/evaluation/user_agent_parsing/
third_party_ua_parser.rs

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