nimbus/stateful/
targeting.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8use crate::enrollment::ExperimentEnrollment;
9use crate::error::{BehaviorError, warn};
10use crate::json::JsonObject;
11use crate::stateful::behavior::{EventQueryType, EventStore};
12use crate::stateful::gecko_prefs::GeckoPrefStore;
13use crate::{NimbusError, NimbusTargetingHelper, Result, TargetingAttributes};
14
15impl NimbusTargetingHelper {
16    pub(crate) fn with_targeting_attributes(
17        targeting_attributes: &TargetingAttributes,
18        event_store: Arc<Mutex<EventStore>>,
19        pref_store: Option<Arc<GeckoPrefStore>>,
20    ) -> Self {
21        Self {
22            context: serde_json::to_value(targeting_attributes.clone()).unwrap(),
23            event_store,
24            gecko_pref_store: pref_store,
25            targeting_attributes: Some(targeting_attributes.clone()),
26        }
27    }
28
29    pub(crate) fn update_enrollment(&mut self, enrollment: &ExperimentEnrollment) -> bool {
30        if let Some(ref mut targeting_attributes) = self.targeting_attributes {
31            targeting_attributes.update_enrollment(enrollment);
32
33            self.context = serde_json::to_value(targeting_attributes.clone()).unwrap();
34            true
35        } else {
36            false
37        }
38    }
39}
40
41#[uniffi::trait_interface]
42pub trait RecordedContext: Send + Sync {
43    /// Returns a JSON representation of the context object
44    ///
45    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
46    fn to_json(&self) -> JsonObject;
47
48    /// Returns a HashMap representation of the event queries that will be used in the targeting
49    /// context
50    ///
51    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
52    fn get_event_queries(&self) -> HashMap<String, String>;
53
54    /// Sets the object's internal value for the event query values
55    ///
56    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
57    fn set_event_query_values(&self, event_query_values: HashMap<String, f64>);
58
59    /// Records the context object to Glean
60    ///
61    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
62    fn record(&self);
63}
64
65impl dyn RecordedContext {
66    pub fn execute_queries(
67        &self,
68        nimbus_targeting_helper: &NimbusTargetingHelper,
69    ) -> Result<HashMap<String, f64>> {
70        let results: HashMap<String, f64> =
71            HashMap::from_iter(self.get_event_queries().iter().filter_map(|(key, query)| {
72                match nimbus_targeting_helper.evaluate_jexl_raw_value(query) {
73                    Ok(result) => match result.as_f64() {
74                        Some(v) => Some((key.clone(), v)),
75                        None => {
76                            warn!(
77                                "Value '{}' for query '{}' was not a string",
78                                result.to_string(),
79                                query
80                            );
81                            None
82                        }
83                    },
84                    Err(err) => {
85                        let error_string = format!(
86                            "error during jexl evaluation for query '{}' — {}",
87                            query, err
88                        );
89                        warn!("{}", error_string);
90                        None
91                    }
92                }
93            }));
94        self.set_event_query_values(results.clone());
95        Ok(results)
96    }
97
98    pub fn validate_queries(&self) -> Result<()> {
99        for query in self.get_event_queries().values() {
100            match EventQueryType::validate_query(query) {
101                Ok(true) => continue,
102                Ok(false) => {
103                    return Err(NimbusError::BehaviorError(
104                        BehaviorError::EventQueryParseError(query.clone()),
105                    ));
106                }
107                Err(err) => return Err(err),
108            }
109        }
110        Ok(())
111    }
112}
113
114pub fn validate_event_queries(recorded_context: Arc<dyn RecordedContext>) -> Result<()> {
115    recorded_context.validate_queries()
116}