use crate::error::{BallError, Result};
use crate::negotiation::{CommitPolicy, FailurePolicy};
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct Contribution {
pub name: String,
pub failure_policy: FailurePolicy,
pub commit_policy: CommitPolicy,
}
#[derive(Debug, PartialEq, Eq)]
pub enum PlanOp {
Apply(usize),
Commit(String),
}
pub fn plan(contributions: &[Contribution], default_msg: &str) -> Result<Vec<PlanOp>> {
for c in contributions {
if matches!(c.commit_policy, CommitPolicy::Suppress)
&& c.failure_policy == FailurePolicy::Required
{
return Err(BallError::Other(format!(
"participant {} returned CommitPolicy::Suppress but is Required for this event; \
a required outcome must be durable",
c.name
)));
}
}
let mut ops = Vec::new();
let mut batches: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut deferred_state = false;
for (i, c) in contributions.iter().enumerate() {
ops.push(PlanOp::Apply(i));
match &c.commit_policy {
CommitPolicy::Commit { message: Some(body) } => {
ops.push(PlanOp::Commit(plugin_commit_message(&c.name, body)));
deferred_state = false;
}
CommitPolicy::Commit { message: None } | CommitPolicy::Suppress => {
deferred_state = true;
}
CommitPolicy::Batch { tag } => {
batches.entry(tag.clone()).or_default().push(c.name.clone());
deferred_state = true;
}
}
}
let any_batch = !batches.is_empty();
for (tag, names) in batches {
ops.push(PlanOp::Commit(batch_commit_message(&tag, &names)));
}
if deferred_state && !any_batch {
ops.push(PlanOp::Commit(default_msg.to_string()));
}
Ok(ops)
}
pub fn plugin_commit_message(name: &str, body: &str) -> String {
let mut lines = body.split('\n');
let first = lines.next().unwrap_or("");
let title = format!("plugin: {name}: {first}");
let rest: Vec<&str> = lines.collect();
if rest.is_empty() {
return title;
}
let body = rest.join("\n");
let body = body.trim_start_matches('\n');
if body.is_empty() {
title
} else {
format!("{title}\n\n{body}")
}
}
pub fn batch_commit_message(tag: &str, names: &[String]) -> String {
format!("balls: batch {tag}\n\nparticipants: {}", names.join(", "))
}
#[cfg(test)]
#[path = "commit_policy_tests.rs"]
mod tests;