use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
struct StringList(Vec<String>);
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
struct BooleanList(Vec<bool>);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum ContextValue {
#[cfg_attr(feature = "utoipa", schema(title = "String"))]
String(String),
#[cfg_attr(feature = "utoipa", schema(title = "Boolean"))]
Boolean(bool),
#[cfg_attr(feature = "utoipa", schema(title = "Number"))]
Number(f64),
#[cfg_attr(feature = "utoipa", schema(title = "DateTime"))]
DateTime(DateTime<Utc>),
#[cfg_attr(feature = "utoipa", schema(title = "StringList", value_type = StringList))]
StringList(Vec<String>),
#[cfg_attr(feature = "utoipa", schema(title = "BooleanList", value_type = BooleanList))]
BooleanList(Vec<bool>),
}
impl ContextValue {
#[must_use]
pub fn as_string(&self) -> Option<&String> {
match self {
ContextValue::String(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_boolean(&self) -> Option<bool> {
match self {
ContextValue::Boolean(b) => Some(*b),
_ => None,
}
}
#[must_use]
pub fn as_number(&self) -> Option<f64> {
match self {
ContextValue::Number(n) => Some(*n),
_ => None,
}
}
#[must_use]
pub fn as_datetime(&self) -> Option<&DateTime<Utc>> {
match self {
ContextValue::DateTime(dt) => Some(dt),
_ => None,
}
}
#[must_use]
pub fn as_string_list(&self) -> Option<&Vec<String>> {
match self {
ContextValue::StringList(list) => Some(list),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Context {
pub data: HashMap<String, ContextValue>,
}
impl Serialize for Context {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.data.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Context {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let data = HashMap::<String, ContextValue>::deserialize(deserializer)?;
Ok(Context { data })
}
}
impl Context {
#[must_use]
pub fn new() -> Self {
Self {
data: HashMap::new(),
}
}
#[must_use]
pub fn with_data(data: HashMap<String, ContextValue>) -> Self {
Self { data }
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&ContextValue> {
self.data.get(key)
}
pub fn insert(&mut self, key: String, value: ContextValue) {
self.data.insert(key, value);
}
#[must_use]
pub fn with_string<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.data
.insert(key.into(), ContextValue::String(value.into()));
self
}
#[must_use]
pub fn with_boolean<K: Into<String>>(mut self, key: K, value: bool) -> Self {
self.data.insert(key.into(), ContextValue::Boolean(value));
self
}
#[must_use]
pub fn with_number<K: Into<String>>(mut self, key: K, value: f64) -> Self {
self.data.insert(key.into(), ContextValue::Number(value));
self
}
#[must_use]
pub fn has_key(&self, key: &str) -> bool {
self.data.contains_key(key)
}
#[must_use]
pub fn keys(&self) -> Vec<&String> {
self.data.keys().collect()
}
pub fn extend(&mut self, other: Context) {
self.data.extend(other.data);
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::float_cmp)]
fn test_context_creation() {
let context = Context::new()
.with_string("key1", "value1")
.with_boolean("key2", true)
.with_number("key3", 42.0);
assert_eq!(context.get("key1").unwrap().as_string().unwrap(), "value1");
assert!(context.get("key2").unwrap().as_boolean().unwrap());
assert_eq!(context.get("key3").unwrap().as_number().unwrap(), 42.0);
}
}