use crate::config::PluginEntry;
use crate::negotiation::FailurePolicy;
use crate::participant::Event;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PolicyKind {
Required,
BestEffort,
Gating,
}
impl PolicyKind {
pub fn into_failure_policy(self) -> FailurePolicy {
match self {
PolicyKind::Required => FailurePolicy::Required,
PolicyKind::BestEffort => FailurePolicy::BestEffort,
PolicyKind::Gating => FailurePolicy::Gating,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventPolicy {
pub policy: PolicyKind,
}
impl EventPolicy {
pub fn new(policy: PolicyKind) -> Self {
Self { policy }
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParticipantConfig {
#[serde(default)]
pub subscriptions: BTreeMap<Event, EventPolicy>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct LocalPluginEntry {
#[serde(default)]
pub participant: Option<ParticipantConfig>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct InvocationOverrides {
pub skip: BTreeSet<String>,
pub required: BTreeSet<String>,
}
impl InvocationOverrides {
pub fn is_empty(&self) -> bool {
self.skip.is_empty() && self.required.is_empty()
}
}
pub fn override_tokens(ov: &InvocationOverrides, sync: bool, no_sync: bool) -> Vec<String> {
let mut out = Vec::new();
if sync {
out.push("--sync".to_string());
} else if no_sync {
out.push("--no-sync".to_string());
}
out.extend(ov.skip.iter().map(|n| format!("--skip={n}")));
out.extend(ov.required.iter().map(|n| format!("--required={n}")));
out
}
pub fn override_log(tokens: &[String]) -> String {
if tokens.is_empty() {
return String::new();
}
let joined = tokens
.iter()
.map(|t| format!("[{t}]"))
.collect::<Vec<_>>()
.join(" ");
format!(" {joined}")
}
pub fn legacy_subscriptions(sync_on_change: bool) -> ParticipantConfig {
let events: &[Event] = if sync_on_change {
&[
Event::Create,
Event::Claim,
Event::Review,
Event::Close,
Event::Update,
Event::Sync,
]
} else {
&[Event::Sync]
};
let subscriptions = events
.iter()
.map(|ev| (*ev, EventPolicy::new(PolicyKind::BestEffort)))
.collect();
ParticipantConfig { subscriptions }
}
pub fn effective_subscriptions(
plugin_name: &str,
repo_entry: &PluginEntry,
local_override: Option<&LocalPluginEntry>,
invocation: &InvocationOverrides,
) -> BTreeMap<Event, FailurePolicy> {
if !repo_entry.enabled || invocation.skip.contains(plugin_name) {
return BTreeMap::new();
}
let base = repo_entry
.participant
.clone()
.unwrap_or_else(|| legacy_subscriptions(repo_entry.sync_on_change));
let mut subs = base.subscriptions;
if let Some(local_part) = local_override.and_then(|l| l.participant.as_ref()) {
for (event, policy) in &local_part.subscriptions {
subs.insert(*event, policy.clone());
}
}
let mut out: BTreeMap<Event, FailurePolicy> = subs
.into_iter()
.map(|(ev, p)| (ev, p.policy.into_failure_policy()))
.collect();
if invocation.required.contains(plugin_name) {
for policy in out.values_mut() {
*policy = FailurePolicy::Required;
}
}
out
}
#[cfg(test)]
#[path = "participant_config_tests.rs"]
mod tests;