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