use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use specta::Type;
use super::ast::BlackboardBinding;
use super::types::BlackboardError;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub(super) struct Blackboard {
values: BTreeMap<String, Value>,
}
impl Blackboard {
pub(super) fn new(values: BTreeMap<String, Value>) -> Self {
Self { values }
}
pub(super) fn get(&self, key: &str) -> Option<&Value> {
self.values.get(key)
}
pub(super) fn set(&mut self, key: impl Into<String>, value: Value) {
self.values.insert(key.into(), value);
}
pub(super) fn values(&self) -> &BTreeMap<String, Value> {
&self.values
}
pub(super) fn bind_inputs(&self, bindings: &[BlackboardBinding]) -> Result<BoundInputs, BlackboardError> {
let mut inputs = BTreeMap::new();
for binding in bindings {
let value = self
.get(&binding.key)
.cloned()
.ok_or_else(|| BlackboardError::MissingInput {
key: binding.key.clone(),
})?;
inputs.insert(binding.name.clone(), value);
}
Ok(BoundInputs { values: inputs })
}
pub(super) fn patch_outputs(
&mut self,
bindings: &[BlackboardBinding],
outputs: &BoundOutputs,
) -> Result<(), BlackboardError> {
for (name, value) in &outputs.values {
let binding = bindings
.iter()
.find(|binding| binding.name == *name)
.ok_or_else(|| BlackboardError::UnknownOutput { name: name.clone() })?;
self.set(binding.key.clone(), value.clone());
}
Ok(())
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Type, schemars::JsonSchema)]
pub struct BoundInputs {
#[serde(default)]
values: BTreeMap<String, Value>,
}
impl BoundInputs {
pub fn get(&self, name: &str) -> Option<&Value> {
self.values.get(name)
}
pub fn as_value(&self) -> Value {
serde_json::to_value(&self.values).expect("JSON values serialize infallibly")
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Type, schemars::JsonSchema)]
pub struct BoundOutputs {
#[serde(default)]
values: BTreeMap<String, Value>,
}
impl BoundOutputs {
pub fn new(values: BTreeMap<String, Value>) -> Self {
Self { values }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binds_inputs_and_applies_only_declared_outputs() {
let mut board = Blackboard::new(BTreeMap::from([(String::from("price"), serde_json::json!(42))]));
let bindings = vec![BlackboardBinding {
name: "input".into(),
key: "price".into(),
}];
assert_eq!(
board.bind_inputs(&bindings).unwrap().get("input"),
Some(&serde_json::json!(42))
);
board
.patch_outputs(
&bindings,
&BoundOutputs::new(BTreeMap::from([(String::from("input"), serde_json::json!(43))])),
)
.unwrap();
assert_eq!(board.get("price"), Some(&serde_json::json!(43)));
assert!(matches!(
board.bind_inputs(&[BlackboardBinding {
name: "missing".into(),
key: "nope".into()
}]),
Err(BlackboardError::MissingInput { .. })
));
assert!(matches!(
board.patch_outputs(
&bindings,
&BoundOutputs::new(BTreeMap::from([(String::from("unknown"), serde_json::json!(1))]))
),
Err(BlackboardError::UnknownOutput { .. })
));
}
}