nimbus/stateful/
targeting.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::{
    enrollment::ExperimentEnrollment,
    error::BehaviorError,
    json::JsonObject,
    stateful::behavior::{EventQueryType, EventStore},
    NimbusError, NimbusTargetingHelper, Result, TargetingAttributes,
};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

impl NimbusTargetingHelper {
    pub(crate) fn with_targeting_attributes(
        targeting_attributes: &TargetingAttributes,
        event_store: Arc<Mutex<EventStore>>,
    ) -> Self {
        Self {
            context: serde_json::to_value(targeting_attributes.clone()).unwrap(),
            event_store,
            targeting_attributes: Some(targeting_attributes.clone()),
        }
    }

    pub(crate) fn update_enrollment(&mut self, enrollment: &ExperimentEnrollment) -> bool {
        if let Some(ref mut targeting_attributes) = self.targeting_attributes {
            targeting_attributes.update_enrollment(enrollment);

            self.context = serde_json::to_value(targeting_attributes.clone()).unwrap();
            true
        } else {
            false
        }
    }
}

pub trait RecordedContext: Send + Sync {
    /// Returns a JSON representation of the context object
    ///
    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
    fn to_json(&self) -> JsonObject;

    /// Returns a HashMap representation of the event queries that will be used in the targeting
    /// context
    ///
    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
    fn get_event_queries(&self) -> HashMap<String, String>;

    /// Sets the object's internal value for the event query values
    ///
    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
    fn set_event_query_values(&self, event_query_values: HashMap<String, f64>);

    /// Records the context object to Glean
    ///
    /// This method will be implemented in foreign code, i.e: Kotlin, Swift, Python, etc...
    fn record(&self);
}

impl dyn RecordedContext {
    pub fn execute_queries(
        &self,
        nimbus_targeting_helper: &NimbusTargetingHelper,
    ) -> Result<HashMap<String, f64>> {
        let results: HashMap<String, f64> =
            HashMap::from_iter(self.get_event_queries().iter().filter_map(|(key, query)| {
                match nimbus_targeting_helper.evaluate_jexl_raw_value(query) {
                    Ok(result) => match result.as_f64() {
                        Some(v) => Some((key.clone(), v)),
                        None => {
                            log::warn!(
                                "Value '{}' for query '{}' was not a string",
                                result.to_string(),
                                query
                            );
                            None
                        }
                    },
                    Err(err) => {
                        let error_string = format!(
                            "error during jexl evaluation for query '{}' — {}",
                            query, err
                        );
                        log::warn!("{}", error_string);
                        None
                    }
                }
            }));
        self.set_event_query_values(results.clone());
        Ok(results)
    }

    pub fn validate_queries(&self) -> Result<()> {
        for query in self.get_event_queries().values() {
            match EventQueryType::validate_query(query) {
                Ok(true) => continue,
                Ok(false) => {
                    return Err(NimbusError::BehaviorError(
                        BehaviorError::EventQueryParseError(query.clone()),
                    ));
                }
                Err(err) => return Err(err),
            }
        }
        Ok(())
    }
}

pub fn validate_event_queries(recorded_context: Arc<dyn RecordedContext>) -> Result<()> {
    recorded_context.validate_queries()
}