use std::collections::BTreeMap;
use std::marker::PhantomData;
use gen_types::TypedDispatcher;
use crate::merge::MergeStrategy;
#[derive(Debug, thiserror::Error)]
pub enum DispatcherError {
#[error("dispatcher missing helpers for variant kinds: {missing:?}")]
MissingCoverage { missing: Vec<String> },
#[error("dispatcher already has a helper for kind '{kind}' — register only once")]
DuplicateHelper { kind: String },
#[error("variant kind '{kind}' is unknown to the dispatcher (not in V::variant_kinds())")]
UnknownKind { kind: String },
}
type Helper<V, C, O> = Box<dyn Fn(&V, &mut C) -> O + Send + Sync + 'static>;
pub struct Dispatcher<V, C, O>
where
V: TypedDispatcher,
{
helpers: BTreeMap<String, Helper<V, C, O>>,
strategy: MergeStrategy,
_marker: PhantomData<fn() -> V>,
}
impl<V, C, O> Default for Dispatcher<V, C, O>
where
V: TypedDispatcher,
{
fn default() -> Self {
Self {
helpers: BTreeMap::new(),
strategy: MergeStrategy::default(),
_marker: PhantomData,
}
}
}
impl<V, C, O> Dispatcher<V, C, O>
where
V: TypedDispatcher,
{
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_strategy(mut self, strategy: MergeStrategy) -> Self {
self.strategy = strategy;
self
}
pub fn helper<F>(mut self, kind: &str, f: F) -> Result<Self, DispatcherError>
where
F: Fn(&V, &mut C) -> O + Send + Sync + 'static,
{
if self.helpers.contains_key(kind) {
return Err(DispatcherError::DuplicateHelper {
kind: kind.to_string(),
});
}
self.helpers.insert(kind.to_string(), Box::new(f));
Ok(self)
}
#[must_use]
pub fn with_helper<F>(self, kind: &str, f: F) -> Self
where
F: Fn(&V, &mut C) -> O + Send + Sync + 'static,
{
self.helper(kind, f).expect("duplicate helper")
}
pub fn into_sealed(self) -> Result<SealedDispatcher<V, C, O>, DispatcherError> {
let universe = V::variant_kinds();
let mut missing = Vec::new();
for k in &universe {
if !self.helpers.contains_key(*k) {
missing.push((*k).to_string());
}
}
if !missing.is_empty() {
return Err(DispatcherError::MissingCoverage { missing });
}
Ok(SealedDispatcher {
helpers: self.helpers,
strategy: self.strategy,
_marker: PhantomData,
})
}
}
pub struct SealedDispatcher<V, C, O>
where
V: TypedDispatcher,
{
helpers: BTreeMap<String, Helper<V, C, O>>,
strategy: MergeStrategy,
_marker: PhantomData<fn() -> V>,
}
impl<V, C, O> SealedDispatcher<V, C, O>
where
V: TypedDispatcher,
O: Default,
{
#[must_use]
pub fn strategy(&self) -> MergeStrategy {
self.strategy
}
pub fn apply_each(&self, variants: &[V], ctx: &mut C) -> Vec<O>
where
V: serde::Serialize,
{
let mut out = Vec::with_capacity(variants.len());
for v in variants {
let kind = serde_variant_kind(v);
if let Some(h) = self.helpers.get(&kind) {
out.push(h(v, ctx));
}
}
out
}
pub fn try_apply_each(
&self,
variants: &[V],
ctx: &mut C,
) -> Result<Vec<O>, DispatcherError>
where
V: serde::Serialize,
{
let mut out = Vec::with_capacity(variants.len());
for v in variants {
let kind = serde_variant_kind(v);
let Some(h) = self.helpers.get(&kind) else {
return Err(DispatcherError::UnknownKind { kind });
};
out.push(h(v, ctx));
}
Ok(out)
}
#[must_use]
pub fn saturation_witness(&self) -> Vec<&str> {
self.helpers.keys().map(String::as_str).collect()
}
#[must_use]
pub fn helper_count(&self) -> usize {
self.helpers.len()
}
}
impl<V, C, O> SealedDispatcher<V, C, O>
where
V: TypedDispatcher + serde::Serialize,
C: Clone,
O: Default + Clone + PartialEq,
{
pub fn is_deterministic(&self, variants: &[V], ctx: &C) -> bool {
let mut a = ctx.clone();
let mut b = ctx.clone();
let r1 = self.apply_each(variants, &mut a);
let r2 = self.apply_each(variants, &mut b);
r1 == r2
}
pub fn is_idempotent(&self, variants: &[V], ctx: &C) -> bool
where
V: Clone,
{
let mut a = ctx.clone();
let r1 = self.apply_each(variants, &mut a);
let mut b = ctx.clone();
let doubled: Vec<V> = variants.iter().chain(variants.iter()).cloned().collect();
let r2 = self.apply_each(&doubled, &mut b);
r2.len() == 2 * r1.len()
&& r2.iter().skip(r1.len()).cloned().collect::<Vec<_>>() == r1
}
}
fn serde_variant_kind<V: serde::Serialize>(v: &V) -> String {
serde_json::to_value(v)
.ok()
.and_then(|val| val.get("kind").and_then(|k| k.as_str().map(String::from)))
.unwrap_or_default()
}