use governor_core::traits::registry::Registry;
use governor_owners::{OwnersDiff, ResolvedOwners, resolve_owners, validate_not_empty};
use serde::Serialize;
use std::collections::HashMap;
use std::path::Path;
use crate::error::{ApplicationError, ApplicationResult};
use crate::ports::{OwnerPackage, OwnersWorkspace, WorkspacePort};
#[derive(Debug, Clone)]
pub struct OwnersShowInput {
pub workspace_path: String,
pub all: bool,
pub explain: bool,
}
#[derive(Debug, Clone)]
pub struct OwnersCheckInput {
pub workspace_path: String,
pub all: bool,
}
#[derive(Debug, Clone)]
pub struct OwnersSyncInput {
pub workspace_path: String,
pub all: bool,
pub dry_run: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnerEntry {
pub owner: String,
pub source: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnersDiffView {
pub to_add: Vec<String>,
pub to_remove: Vec<String>,
}
impl From<&OwnersDiff> for OwnersDiffView {
fn from(value: &OwnersDiff) -> Self {
Self {
to_add: value.to_add.clone(),
to_remove: value.to_remove.clone(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnersPackageReport {
pub name: String,
pub owners: Vec<OwnerEntry>,
pub total: usize,
pub diff: Option<OwnersDiffView>,
pub sync: Option<OwnersSyncStatus>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnersSyncStatus {
pub status: String,
pub diff: Option<OwnersDiffView>,
pub errors: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnersShowOutput {
pub workspace: String,
pub packages: Vec<OwnersPackageReport>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnersCheckOutput {
pub workspace: String,
pub drift_detected: bool,
pub packages: Vec<OwnersPackageReport>,
}
#[derive(Debug, Clone, Serialize)]
pub struct OwnersSyncOutput {
pub workspace: String,
pub partial_success: bool,
pub packages: Vec<OwnersPackageReport>,
}
fn selected_packages(workspace: &OwnersWorkspace, all: bool) -> Vec<&OwnerPackage> {
if all {
workspace.packages.iter().collect()
} else {
workspace.packages.first().into_iter().collect()
}
}
fn resolve_package(
workspace: &OwnersWorkspace,
pkg: &OwnerPackage,
) -> ApplicationResult<ResolvedOwners> {
let workspace_config = workspace.workspace.clone().unwrap_or_default();
let package_config = pkg.owners.clone().unwrap_or_default();
let resolved = resolve_owners(&workspace_config, &package_config);
validate_not_empty(&resolved.owners, &pkg.name)
.map_err(|err| ApplicationError::InvalidArguments(err.to_string()))?;
Ok(resolved)
}
fn owner_entries(resolved: &ResolvedOwners, explain: bool) -> Vec<OwnerEntry> {
resolved
.owners
.iter()
.map(|owner| OwnerEntry {
owner: owner.clone(),
source: explain
.then(|| resolved.sources.get(owner).cloned())
.flatten(),
})
.collect()
}
pub async fn show<W: WorkspacePort>(
workspace_port: &W,
input: OwnersShowInput,
) -> ApplicationResult<OwnersShowOutput> {
let workspace = workspace_port
.load_owners_workspace(Path::new(&input.workspace_path))
.await?;
let reports = selected_packages(&workspace, input.all)
.into_iter()
.map(|pkg| {
let resolved = resolve_package(&workspace, pkg)?;
Ok(OwnersPackageReport {
name: pkg.name.clone(),
owners: owner_entries(&resolved, input.explain),
total: resolved.len(),
diff: None,
sync: None,
})
})
.collect::<ApplicationResult<Vec<_>>>()?;
Ok(OwnersShowOutput {
workspace: workspace.root.display().to_string(),
packages: reports,
})
}
pub async fn check<W: WorkspacePort, R: Registry>(
workspace_port: &W,
registry: &R,
input: OwnersCheckInput,
) -> ApplicationResult<OwnersCheckOutput> {
let workspace = workspace_port
.load_owners_workspace(Path::new(&input.workspace_path))
.await?;
let mut reports = Vec::new();
let mut drift_detected = false;
for pkg in selected_packages(&workspace, input.all) {
let resolved = resolve_package(&workspace, pkg)?;
let current = registry
.list_owners(&pkg.name)
.await?
.into_iter()
.map(|owner| owner.username)
.collect::<Vec<_>>();
let diff = OwnersDiff::calculate(¤t, &resolved.owners);
drift_detected |= !diff.is_empty();
reports.push(OwnersPackageReport {
name: pkg.name.clone(),
owners: owner_entries(&resolved, true),
total: resolved.len(),
diff: Some(OwnersDiffView::from(&diff)),
sync: None,
});
}
Ok(OwnersCheckOutput {
workspace: workspace.root.display().to_string(),
drift_detected,
packages: reports,
})
}
pub async fn sync<W: WorkspacePort, R: Registry>(
workspace_port: &W,
registry: &R,
input: OwnersSyncInput,
) -> ApplicationResult<OwnersSyncOutput> {
let workspace = workspace_port
.load_owners_workspace(Path::new(&input.workspace_path))
.await?;
let mut reports = Vec::new();
let mut partial_success = false;
for pkg in selected_packages(&workspace, input.all) {
let resolved = resolve_package(&workspace, pkg)?;
let current = registry
.list_owners(&pkg.name)
.await?
.into_iter()
.map(|owner| owner.username)
.collect::<Vec<_>>();
let diff = OwnersDiff::calculate(¤t, &resolved.owners);
let sync = if diff.is_empty() {
OwnersSyncStatus {
status: "already_in_sync".to_string(),
diff: None,
errors: HashMap::new(),
}
} else if input.dry_run {
OwnersSyncStatus {
status: "dry_run".to_string(),
diff: Some(OwnersDiffView::from(&diff)),
errors: HashMap::new(),
}
} else {
let mut errors = HashMap::new();
for owner in &diff.to_add {
if let Err(error) = registry.add_owner(&pkg.name, owner).await {
errors.insert(owner.clone(), error.to_string());
}
}
for owner in &diff.to_remove {
if let Err(error) = registry.remove_owner(&pkg.name, owner).await {
errors.insert(owner.clone(), error.to_string());
}
}
let status = if errors.is_empty() {
"success"
} else {
partial_success = true;
"partial"
};
OwnersSyncStatus {
status: status.to_string(),
diff: Some(OwnersDiffView::from(&diff)),
errors,
}
};
reports.push(OwnersPackageReport {
name: pkg.name.clone(),
owners: owner_entries(&resolved, true),
total: resolved.len(),
diff: Some(OwnersDiffView::from(&diff)),
sync: Some(sync),
});
}
Ok(OwnersSyncOutput {
workspace: workspace.root.display().to_string(),
partial_success,
packages: reports,
})
}