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
65pub fn execute_event_queries(
66    recorded_context: &dyn RecordedContext,
67    nimbus_targeting_helper: &NimbusTargetingHelper,
68) -> Result<HashMap<String, f64>> {
69    let results: HashMap<String, f64> =
70        HashMap::from_iter(recorded_context.get_event_queries().iter().filter_map(
71            |(key, query)| match nimbus_targeting_helper.evaluate_jexl_raw_value(query) {
72                Ok(result) => match result.as_f64() {
73                    Some(v) => Some((key.clone(), v)),
74                    None => {
75                        warn!(
76                            "Value '{}' for query '{}' was not a string",
77                            result.to_string(),
78                            query
79                        );
80                        None
81                    }
82                },
83                Err(err) => {
84                    let error_string = format!(
85                        "error during jexl evaluation for query '{}' — {}",
86                        query, err
87                    );
88                    warn!("{}", error_string);
89                    None
90                }
91            },
92        ));
93    recorded_context.set_event_query_values(results.clone());
94    Ok(results)
95}
96
97pub fn validate_event_queries(recorded_context: Arc<dyn RecordedContext>) -> Result<()> {
98    for query in recorded_context.get_event_queries().values() {
99        match EventQueryType::validate_query(query) {
100            Ok(true) => continue,
101            Ok(false) => {
102                return Err(NimbusError::BehaviorError(
103                    BehaviorError::EventQueryParseError(query.clone()),
104                ));
105            }
106            Err(err) => return Err(err),
107        }
108    }
109    Ok(())
110}