#![allow(clippy::module_name_repetitions)]
use std::collections::HashMap;
use anyhow::Result;
#[cfg(feature = "time")]
use chrono::TimeZone;
use serde::{de::DeserializeOwned, Serialize};
pub trait EvaluationContext: Send + 'static {
#[cfg(feature = "rng")]
type Rng: rand::Rng;
#[cfg(feature = "rng")]
fn get_rng(&mut self) -> Self::Rng;
#[cfg(feature = "time")]
fn now(&self) -> chrono::DateTime<chrono::Utc>;
fn evaluation_start(&mut self);
fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>>;
fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()>;
}
pub struct DefaultContext {
cache: HashMap<String, serde_json::Value>,
#[cfg(feature = "time")]
evaluation_time: chrono::DateTime<chrono::Utc>,
}
#[allow(clippy::derivable_impls)]
impl Default for DefaultContext {
fn default() -> Self {
Self {
cache: HashMap::new(),
#[cfg(feature = "time")]
evaluation_time: chrono::Utc.timestamp_nanos(0),
}
}
}
impl EvaluationContext for DefaultContext {
#[cfg(feature = "rng")]
type Rng = rand::rngs::ThreadRng;
#[cfg(feature = "rng")]
fn get_rng(&mut self) -> Self::Rng {
rand::thread_rng()
}
#[cfg(feature = "time")]
fn now(&self) -> chrono::DateTime<chrono::Utc> {
self.evaluation_time
}
fn evaluation_start(&mut self) {
self.cache = HashMap::new();
#[cfg(feature = "time")]
{
self.evaluation_time = chrono::Utc::now();
}
}
fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>> {
let key = serde_json::to_string(&key)?;
let Some(value) = self.cache.get(&key) else {
return Ok(None);
};
let value = serde_json::from_value(value.clone())?;
Ok(value)
}
fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()> {
let key = serde_json::to_string(key)?;
let content = serde_json::to_value(content)?;
self.cache.insert(key, content);
Ok(())
}
}
pub mod tests {
use anyhow::Result;
#[cfg(feature = "time")]
use chrono::TimeZone;
use serde::{de::DeserializeOwned, Serialize};
use crate::{DefaultContext, EvaluationContext};
pub struct TestContext {
inner: DefaultContext,
#[cfg(feature = "time")]
clock: chrono::DateTime<chrono::Utc>,
#[cfg(feature = "rng")]
seed: u64,
}
#[allow(clippy::derivable_impls)]
impl Default for TestContext {
fn default() -> Self {
Self {
inner: DefaultContext::default(),
#[cfg(feature = "time")]
clock: chrono::Utc
.timestamp_opt(1_594_731_202, 0)
.unwrap(),
#[cfg(feature = "rng")]
seed: 0,
}
}
}
impl EvaluationContext for TestContext {
#[cfg(feature = "rng")]
type Rng = rand::rngs::StdRng;
fn evaluation_start(&mut self) {
self.inner.evaluation_start();
}
#[cfg(feature = "time")]
fn now(&self) -> chrono::DateTime<chrono::Utc> {
self.clock
}
#[cfg(feature = "rng")]
fn get_rng(&mut self) -> Self::Rng {
use rand::SeedableRng;
rand::rngs::StdRng::seed_from_u64(self.seed)
}
fn cache_get<K: Serialize, C: DeserializeOwned>(&mut self, key: &K) -> Result<Option<C>> {
self.inner.cache_get(key)
}
fn cache_set<K: Serialize, C: Serialize>(&mut self, key: &K, content: &C) -> Result<()> {
self.inner.cache_set(key, content)
}
}
}