use std::path::PathBuf;
use crate::scope::Scope;
#[must_use]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct InstallPlan {
pub target: PlanTarget,
pub changes: Vec<PlannedChange>,
pub status: PlanStatus,
pub warnings: Vec<PlanWarning>,
}
impl InstallPlan {
pub(crate) fn from_changes(target: PlanTarget, changes: Vec<PlannedChange>) -> Self {
Self {
target,
status: status_for_changes(&changes),
changes,
warnings: Vec::new(),
}
}
pub(crate) fn refused(
target: PlanTarget,
path: Option<PathBuf>,
reason: RefusalReason,
) -> Self {
Self::from_changes(target, vec![PlannedChange::Refuse { path, reason }])
}
}
#[must_use]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct UninstallPlan {
pub target: PlanTarget,
pub changes: Vec<PlannedChange>,
pub status: PlanStatus,
pub warnings: Vec<PlanWarning>,
}
impl UninstallPlan {
pub(crate) fn from_changes(target: PlanTarget, changes: Vec<PlannedChange>) -> Self {
Self {
target,
status: status_for_changes(&changes),
changes,
warnings: Vec::new(),
}
}
pub(crate) fn refused(
target: PlanTarget,
path: Option<PathBuf>,
reason: RefusalReason,
) -> Self {
Self::from_changes(target, vec![PlannedChange::Refuse { path, reason }])
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum PlanTarget {
Hook {
integration_id: &'static str,
scope: Scope,
tag: String,
},
Mcp {
integration_id: &'static str,
scope: Scope,
name: String,
owner: String,
},
Skill {
integration_id: &'static str,
scope: Scope,
name: String,
owner: String,
},
Instruction {
integration_id: &'static str,
scope: Scope,
name: String,
owner: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PlanStatus {
WillChange,
NoOp,
Refused,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PlannedChange {
CreateFile {
path: PathBuf,
},
PatchFile {
path: PathBuf,
},
RemoveFile {
path: PathBuf,
},
RestoreBackup {
backup: PathBuf,
target: PathBuf,
},
CreateBackup {
backup: PathBuf,
target: PathBuf,
},
CreateDir {
path: PathBuf,
},
RemoveDir {
path: PathBuf,
},
WriteLedger {
path: PathBuf,
key: String,
owner: String,
},
RemoveLedgerEntry {
path: PathBuf,
key: String,
},
SetPermissions {
path: PathBuf,
mode: u32,
},
NoOp {
path: PathBuf,
reason: String,
},
Refuse {
path: Option<PathBuf>,
reason: RefusalReason,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RefusalReason {
OwnerMismatch,
UserInstalledEntry,
InvalidConfig,
BackupAlreadyExists,
UnsupportedScope,
MissingRequiredSpecField,
InlineSecretInLocalScope,
UnsupportedTransport,
UnsupportedPlatform,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PlanWarning {
pub path: Option<PathBuf>,
pub message: String,
}
fn status_for_changes(changes: &[PlannedChange]) -> PlanStatus {
if has_refusal(changes) {
return PlanStatus::Refused;
}
if changes.is_empty()
|| changes
.iter()
.all(|c| matches!(c, PlannedChange::NoOp { .. }))
{
return PlanStatus::NoOp;
}
PlanStatus::WillChange
}
pub(crate) fn has_refusal(changes: &[PlannedChange]) -> bool {
changes
.iter()
.any(|c| matches!(c, PlannedChange::Refuse { .. }))
}