winbrew_app\operations\remove/
plan.rs1use crate::database;
13use crate::models::domains::install::RemovalPlan;
14use crate::models::domains::installed::InstalledPackage;
15
16use super::Result;
17
18pub fn find_dependents(name: &str, conn: &database::DbConnection) -> Result<Vec<String>> {
24 let mut dependents = database::list_packages(conn)?
25 .into_iter()
26 .filter(|pkg| {
27 pkg.name != name
28 && pkg
29 .dependencies
30 .iter()
31 .any(|dep| dependency_name(dep).eq_ignore_ascii_case(name))
32 })
33 .map(|pkg| pkg.name)
34 .collect::<Vec<_>>();
35
36 dependents.sort_unstable();
37 dependents.dedup();
38
39 Ok(dependents)
40}
41
42pub fn plan_removal(name: &str) -> Result<RemovalPlan> {
49 let conn = database::get_conn()?;
50 let pkg = database::get_package(&conn, name)?.ok_or_else(|| {
51 anyhow::Error::new(database::PackageNotFoundError {
52 name: name.to_string(),
53 })
54 })?;
55 let dependents = find_dependents(name, &conn)?;
56
57 Ok(removal_plan(pkg, dependents))
58}
59
60fn removal_plan(pkg: InstalledPackage, dependents: Vec<String>) -> RemovalPlan {
65 RemovalPlan {
66 package: pkg,
67 dependents,
68 }
69}
70
71fn dependency_name(dep: &str) -> &str {
76 dep.split_once('@').map_or(dep, |(name, _)| name)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::removal_plan;
82 use crate::models::domains::install::EngineMetadata;
83 use crate::models::domains::install::InstallScope;
84 use crate::models::domains::install::InstallerType;
85 use crate::models::domains::installed::{InstalledPackage, PackageStatus};
86
87 fn package(
88 name: &str,
89 kind: InstallerType,
90 install_dir: &str,
91 engine_metadata: Option<EngineMetadata>,
92 ) -> InstalledPackage {
93 InstalledPackage {
94 name: name.to_string(),
95 version: "1.0.0".to_string(),
96 kind,
97 deployment_kind: kind.deployment_kind(),
98 engine_kind: kind.into(),
99 engine_metadata,
100 install_dir: install_dir.to_string(),
101 dependencies: Vec::new(),
102 status: PackageStatus::Ok,
103 installed_at: "2026-04-05T00:00:00Z".to_string(),
104 }
105 }
106
107 #[test]
108 fn removal_plan_preserves_engine_metadata() {
109 let plan = removal_plan(
110 package(
111 "Contoso.App",
112 InstallerType::Msix,
113 r"C:\Packages\Contoso.App",
114 Some(EngineMetadata::msix(
115 "Contoso.App_1.0.0_x64__8wekyb3d8bbwe",
116 InstallScope::Installed,
117 )),
118 ),
119 vec!["Contoso.Consumer".to_string()],
120 );
121
122 assert_eq!(plan.package.name, "Contoso.App");
123 assert_eq!(
124 plan.package.engine_metadata,
125 Some(EngineMetadata::msix(
126 "Contoso.App_1.0.0_x64__8wekyb3d8bbwe",
127 InstallScope::Installed,
128 ))
129 );
130 assert_eq!(plan.dependents, vec!["Contoso.Consumer".to_string()]);
131 }
132}