opa_wasm/
context.rs

1// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Trait definition for the context passed through builtin evaluation
16
17#![allow(clippy::module_name_repetitions)]
18
19use std::collections::HashMap;
20
21use anyhow::Result;
22#[cfg(feature = "time")]
23use chrono::TimeZone;
24use serde::{de::DeserializeOwned, Serialize};
25
26/// Context passed through builtin evaluation
27pub trait EvaluationContext: Send + 'static {
28    /// The type of random number generator used by this context
29    #[cfg(feature = "rng")]
30    type Rng: rand::Rng;
31
32    /// Get a [`rand::Rng`]
33    #[cfg(feature = "rng")]
34    fn get_rng(&mut self) -> Self::Rng;
35
36    /// Get the current date and time
37    #[cfg(feature = "time")]
38    fn now(&self) -> chrono::DateTime<chrono::Utc>;
39
40    /// Notify the context on evaluation start, so it can clean itself up
41    fn evaluation_start(&mut self);
42
43    /// Get a value from the evaluation cache
44    ///
45    /// # Errors
46    ///
47    /// If the key failed to serialize, or the value failed to deserialize
48    fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>>;
49
50    /// Push a value to the evaluation cache
51    ///
52    /// # Errors
53    ///
54    /// If the key or the value failed to serialize
55    fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()>;
56}
57
58/// The default evaluation context implementation
59pub struct DefaultContext {
60    /// The cache used to store values during evaluation
61    cache: HashMap<String, serde_json::Value>,
62
63    /// The time at which the evaluation started
64    #[cfg(feature = "time")]
65    evaluation_time: chrono::DateTime<chrono::Utc>,
66}
67
68#[allow(clippy::derivable_impls)]
69impl Default for DefaultContext {
70    fn default() -> Self {
71        Self {
72            cache: HashMap::new(),
73
74            #[cfg(feature = "time")]
75            evaluation_time: chrono::Utc.timestamp_nanos(0),
76        }
77    }
78}
79
80impl EvaluationContext for DefaultContext {
81    #[cfg(feature = "rng")]
82    type Rng = rand::rngs::ThreadRng;
83
84    #[cfg(feature = "rng")]
85    fn get_rng(&mut self) -> Self::Rng {
86        rand::thread_rng()
87    }
88
89    #[cfg(feature = "time")]
90    fn now(&self) -> chrono::DateTime<chrono::Utc> {
91        self.evaluation_time
92    }
93
94    fn evaluation_start(&mut self) {
95        // Clear the cache
96        self.cache = HashMap::new();
97
98        #[cfg(feature = "time")]
99        {
100            // Set the evaluation time to now
101            self.evaluation_time = chrono::Utc::now();
102        }
103    }
104
105    fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>> {
106        let key = serde_json::to_string(&key)?;
107        let Some(value) = self.cache.get(&key) else {
108            return Ok(None);
109        };
110
111        let value = serde_json::from_value(value.clone())?;
112        Ok(value)
113    }
114
115    fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()> {
116        let key = serde_json::to_string(key)?;
117        let content = serde_json::to_value(content)?;
118        self.cache.insert(key, content);
119        Ok(())
120    }
121}
122
123/// Test utilities
124pub mod tests {
125    use anyhow::Result;
126    #[cfg(feature = "time")]
127    use chrono::TimeZone;
128    use serde::{de::DeserializeOwned, Serialize};
129
130    use crate::{DefaultContext, EvaluationContext};
131
132    /// A context used in tests
133    pub struct TestContext {
134        /// The inner [`DefaultContext`]
135        inner: DefaultContext,
136
137        /// The mocked time
138        #[cfg(feature = "time")]
139        clock: chrono::DateTime<chrono::Utc>,
140
141        /// The seed used for the random number generator
142        #[cfg(feature = "rng")]
143        seed: u64,
144    }
145
146    #[allow(clippy::derivable_impls)]
147    impl Default for TestContext {
148        fn default() -> Self {
149            Self {
150                inner: DefaultContext::default(),
151
152                #[cfg(feature = "time")]
153                clock: chrono::Utc
154                    // Corresponds to 2020-07-14T12:53:22Z
155                    // We're using this method because it's available on old versions of chrono
156                    .timestamp_opt(1_594_731_202, 0)
157                    .unwrap(),
158
159                #[cfg(feature = "rng")]
160                seed: 0,
161            }
162        }
163    }
164
165    impl EvaluationContext for TestContext {
166        #[cfg(feature = "rng")]
167        type Rng = rand::rngs::StdRng;
168
169        fn evaluation_start(&mut self) {
170            self.inner.evaluation_start();
171        }
172
173        #[cfg(feature = "time")]
174        fn now(&self) -> chrono::DateTime<chrono::Utc> {
175            self.clock
176        }
177
178        #[cfg(feature = "rng")]
179        fn get_rng(&mut self) -> Self::Rng {
180            use rand::SeedableRng;
181
182            rand::rngs::StdRng::seed_from_u64(self.seed)
183        }
184
185        fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>> {
186            self.inner.cache_get(key)
187        }
188
189        fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()> {
190            self.inner.cache_set(key, content)
191        }
192    }
193}