use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub mod prefix {
pub const APP: &str = "app:";
pub const USER: &str = "user:";
pub const TEMP: &str = "temp:";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StateScope {
App,
User,
Temp,
Session,
}
impl StateScope {
#[must_use]
pub fn of(key: &str) -> Self {
if key.starts_with(prefix::APP) {
Self::App
} else if key.starts_with(prefix::USER) {
Self::User
} else if key.starts_with(prefix::TEMP) {
Self::Temp
} else {
Self::Session
}
}
}
pub type StateDelta = IndexMap<String, Value>;
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct State {
pub map: IndexMap<String, Value>,
}
impl State {
pub fn new() -> Self {
Self::default()
}
pub fn from_iter<I: IntoIterator<Item = (String, Value)>>(it: I) -> Self {
Self {
map: it.into_iter().collect(),
}
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&Value> {
self.map.get(key)
}
#[must_use]
pub fn scope(key: &str) -> StateScope {
StateScope::of(key)
}
pub fn set(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
self.map.insert(key.into(), value)
}
pub fn apply(&mut self, delta: &StateDelta) {
for (k, v) in delta {
self.map.insert(k.clone(), v.clone());
}
}
#[must_use]
pub fn partition_by_scope(
delta: &StateDelta,
) -> (StateDelta, StateDelta, StateDelta, StateDelta) {
let (mut app, mut user, mut session, mut temp) = (
StateDelta::new(),
StateDelta::new(),
StateDelta::new(),
StateDelta::new(),
);
for (k, v) in delta {
match StateScope::of(k) {
StateScope::App => {
app.insert(k.clone(), v.clone());
}
StateScope::User => {
user.insert(k.clone(), v.clone());
}
StateScope::Session => {
session.insert(k.clone(), v.clone());
}
StateScope::Temp => {
temp.insert(k.clone(), v.clone());
}
}
}
(app, user, session, temp)
}
#[must_use]
pub fn len(&self) -> usize {
self.map.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
self.map.iter()
}
#[must_use]
pub fn trim_temp_keys(delta: &StateDelta) -> StateDelta {
delta
.iter()
.filter(|(k, _)| StateScope::of(k) != StateScope::Temp)
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn scope_detection() {
assert_eq!(StateScope::of("app:foo"), StateScope::App);
assert_eq!(StateScope::of("user:foo"), StateScope::User);
assert_eq!(StateScope::of("temp:foo"), StateScope::Temp);
assert_eq!(StateScope::of("plain"), StateScope::Session);
}
#[test]
fn apply_inserts_keys() {
let mut s = State::new();
let mut d = StateDelta::new();
d.insert("a".into(), json!(1));
d.insert("temp:b".into(), json!(2));
s.apply(&d);
assert_eq!(s.get("a"), Some(&json!(1)));
assert_eq!(s.get("temp:b"), Some(&json!(2)));
}
#[test]
fn trim_temp_removes_temp_keys() {
let mut d = StateDelta::new();
d.insert("a".into(), json!(1));
d.insert("temp:b".into(), json!(2));
let t = State::trim_temp_keys(&d);
assert!(t.contains_key("a"));
assert!(!t.contains_key("temp:b"));
}
}