1use governor_core::traits::registry::Registry;
4use governor_owners::{OwnersDiff, ResolvedOwners, resolve_owners, validate_not_empty};
5use serde::Serialize;
6use std::collections::HashMap;
7use std::path::Path;
8
9use crate::error::{ApplicationError, ApplicationResult};
10use crate::ports::{OwnerPackage, OwnersWorkspace, WorkspacePort};
11
12#[derive(Debug, Clone)]
14pub struct OwnersShowInput {
15 pub workspace_path: String,
17 pub all: bool,
19 pub explain: bool,
21}
22
23#[derive(Debug, Clone)]
25pub struct OwnersCheckInput {
26 pub workspace_path: String,
28 pub all: bool,
30}
31
32#[derive(Debug, Clone)]
34pub struct OwnersSyncInput {
35 pub workspace_path: String,
37 pub all: bool,
39 pub dry_run: bool,
41}
42
43#[derive(Debug, Clone, Serialize)]
45pub struct OwnerEntry {
46 pub owner: String,
48 pub source: Option<String>,
50}
51
52#[derive(Debug, Clone, Serialize)]
53pub struct OwnersDiffView {
54 pub to_add: Vec<String>,
55 pub to_remove: Vec<String>,
56}
57
58impl From<&OwnersDiff> for OwnersDiffView {
59 fn from(value: &OwnersDiff) -> Self {
60 Self {
61 to_add: value.to_add.clone(),
62 to_remove: value.to_remove.clone(),
63 }
64 }
65}
66
67#[derive(Debug, Clone, Serialize)]
69pub struct OwnersPackageReport {
70 pub name: String,
72 pub owners: Vec<OwnerEntry>,
74 pub total: usize,
76 pub diff: Option<OwnersDiffView>,
78 pub sync: Option<OwnersSyncStatus>,
80}
81
82#[derive(Debug, Clone, Serialize)]
84pub struct OwnersSyncStatus {
85 pub status: String,
87 pub diff: Option<OwnersDiffView>,
89 pub errors: HashMap<String, String>,
91}
92
93#[derive(Debug, Clone, Serialize)]
95pub struct OwnersShowOutput {
96 pub workspace: String,
98 pub packages: Vec<OwnersPackageReport>,
100}
101
102#[derive(Debug, Clone, Serialize)]
104pub struct OwnersCheckOutput {
105 pub workspace: String,
107 pub drift_detected: bool,
109 pub packages: Vec<OwnersPackageReport>,
111}
112
113#[derive(Debug, Clone, Serialize)]
115pub struct OwnersSyncOutput {
116 pub workspace: String,
118 pub partial_success: bool,
120 pub packages: Vec<OwnersPackageReport>,
122}
123
124fn selected_packages(workspace: &OwnersWorkspace, all: bool) -> Vec<&OwnerPackage> {
125 if all {
126 workspace.packages.iter().collect()
127 } else {
128 workspace.packages.first().into_iter().collect()
129 }
130}
131
132fn resolve_package(
133 workspace: &OwnersWorkspace,
134 pkg: &OwnerPackage,
135) -> ApplicationResult<ResolvedOwners> {
136 let workspace_config = workspace.workspace.clone().unwrap_or_default();
137 let package_config = pkg.owners.clone().unwrap_or_default();
138 let resolved = resolve_owners(&workspace_config, &package_config);
139 validate_not_empty(&resolved.owners, &pkg.name)
140 .map_err(|err| ApplicationError::InvalidArguments(err.to_string()))?;
141 Ok(resolved)
142}
143
144fn owner_entries(resolved: &ResolvedOwners, explain: bool) -> Vec<OwnerEntry> {
145 resolved
146 .owners
147 .iter()
148 .map(|owner| OwnerEntry {
149 owner: owner.clone(),
150 source: explain
151 .then(|| resolved.sources.get(owner).cloned())
152 .flatten(),
153 })
154 .collect()
155}
156
157pub async fn show<W: WorkspacePort>(
164 workspace_port: &W,
165 input: OwnersShowInput,
166) -> ApplicationResult<OwnersShowOutput> {
167 let workspace = workspace_port
168 .load_owners_workspace(Path::new(&input.workspace_path))
169 .await?;
170 let reports = selected_packages(&workspace, input.all)
171 .into_iter()
172 .map(|pkg| {
173 let resolved = resolve_package(&workspace, pkg)?;
174 Ok(OwnersPackageReport {
175 name: pkg.name.clone(),
176 owners: owner_entries(&resolved, input.explain),
177 total: resolved.len(),
178 diff: None,
179 sync: None,
180 })
181 })
182 .collect::<ApplicationResult<Vec<_>>>()?;
183
184 Ok(OwnersShowOutput {
185 workspace: workspace.root.display().to_string(),
186 packages: reports,
187 })
188}
189
190pub async fn check<W: WorkspacePort, R: Registry>(
197 workspace_port: &W,
198 registry: &R,
199 input: OwnersCheckInput,
200) -> ApplicationResult<OwnersCheckOutput> {
201 let workspace = workspace_port
202 .load_owners_workspace(Path::new(&input.workspace_path))
203 .await?;
204 let mut reports = Vec::new();
205 let mut drift_detected = false;
206
207 for pkg in selected_packages(&workspace, input.all) {
208 let resolved = resolve_package(&workspace, pkg)?;
209 let current = registry
210 .list_owners(&pkg.name)
211 .await?
212 .into_iter()
213 .map(|owner| owner.username)
214 .collect::<Vec<_>>();
215 let diff = OwnersDiff::calculate(¤t, &resolved.owners);
216 drift_detected |= !diff.is_empty();
217
218 reports.push(OwnersPackageReport {
219 name: pkg.name.clone(),
220 owners: owner_entries(&resolved, true),
221 total: resolved.len(),
222 diff: Some(OwnersDiffView::from(&diff)),
223 sync: None,
224 });
225 }
226
227 Ok(OwnersCheckOutput {
228 workspace: workspace.root.display().to_string(),
229 drift_detected,
230 packages: reports,
231 })
232}
233
234pub async fn sync<W: WorkspacePort, R: Registry>(
241 workspace_port: &W,
242 registry: &R,
243 input: OwnersSyncInput,
244) -> ApplicationResult<OwnersSyncOutput> {
245 let workspace = workspace_port
246 .load_owners_workspace(Path::new(&input.workspace_path))
247 .await?;
248 let mut reports = Vec::new();
249 let mut partial_success = false;
250
251 for pkg in selected_packages(&workspace, input.all) {
252 let resolved = resolve_package(&workspace, pkg)?;
253 let current = registry
254 .list_owners(&pkg.name)
255 .await?
256 .into_iter()
257 .map(|owner| owner.username)
258 .collect::<Vec<_>>();
259 let diff = OwnersDiff::calculate(¤t, &resolved.owners);
260
261 let sync = if diff.is_empty() {
262 OwnersSyncStatus {
263 status: "already_in_sync".to_string(),
264 diff: None,
265 errors: HashMap::new(),
266 }
267 } else if input.dry_run {
268 OwnersSyncStatus {
269 status: "dry_run".to_string(),
270 diff: Some(OwnersDiffView::from(&diff)),
271 errors: HashMap::new(),
272 }
273 } else {
274 let mut errors = HashMap::new();
275 for owner in &diff.to_add {
276 if let Err(error) = registry.add_owner(&pkg.name, owner).await {
277 errors.insert(owner.clone(), error.to_string());
278 }
279 }
280 for owner in &diff.to_remove {
281 if let Err(error) = registry.remove_owner(&pkg.name, owner).await {
282 errors.insert(owner.clone(), error.to_string());
283 }
284 }
285
286 let status = if errors.is_empty() {
287 "success"
288 } else {
289 partial_success = true;
290 "partial"
291 };
292
293 OwnersSyncStatus {
294 status: status.to_string(),
295 diff: Some(OwnersDiffView::from(&diff)),
296 errors,
297 }
298 };
299
300 reports.push(OwnersPackageReport {
301 name: pkg.name.clone(),
302 owners: owner_entries(&resolved, true),
303 total: resolved.len(),
304 diff: Some(OwnersDiffView::from(&diff)),
305 sync: Some(sync),
306 });
307 }
308
309 Ok(OwnersSyncOutput {
310 workspace: workspace.root.display().to_string(),
311 partial_success,
312 packages: reports,
313 })
314}