1#![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
26pub trait EvaluationContext: Send + 'static {
28 #[cfg(feature = "rng")]
30 type Rng: rand::Rng;
31
32 #[cfg(feature = "rng")]
34 fn get_rng(&mut self) -> Self::Rng;
35
36 #[cfg(feature = "time")]
38 fn now(&self) -> chrono::DateTime<chrono::Utc>;
39
40 fn evaluation_start(&mut self);
42
43 fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>>;
49
50 fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()>;
56}
57
58pub struct DefaultContext {
60 cache: HashMap<String, serde_json::Value>,
62
63 #[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 self.cache = HashMap::new();
97
98 #[cfg(feature = "time")]
99 {
100 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
123pub 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 pub struct TestContext {
134 inner: DefaultContext,
136
137 #[cfg(feature = "time")]
139 clock: chrono::DateTime<chrono::Utc>,
140
141 #[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 .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}