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 crate::stateful::gecko_prefs::GeckoPrefStore;
6use crate::{
7    enrollment::ExperimentEnrollment,
8    error::{warn, BehaviorError},
9    json::JsonObject,
10    stateful::behavior::{EventQueryType, EventStore},
11    NimbusError, NimbusTargetingHelper, Result, TargetingAttributes,
12};
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15
16impl NimbusTargetingHelper {
17    pub(crate) fn with_targeting_attributes(
18        targeting_attributes: &TargetingAttributes,
19        event_store: Arc<Mutex<EventStore>>,
20        pref_store: Option<Arc<GeckoPrefStore>>,
21    ) -> Self {
22        Self {
23            context: serde_json::to_value(targeting_attributes.clone()).unwrap(),
24            event_store,
25            gecko_pref_store: pref_store,
26            targeting_attributes: Some(targeting_attributes.clone()),
27        }
28    }
29
30    pub(crate) fn update_enrollment(&mut self, enrollment: &ExperimentEnrollment) -> bool {
31        if let Some(ref mut targeting_attributes) = self.targeting_attributes {
32            targeting_attributes.update_enrollment(enrollment);
33
34            self.context = serde_json::to_value(targeting_attributes.clone()).unwrap();
35            true
36        } else {
37            false
38        }
39    }
40}
41
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}