use crate::error::{Error, Result};
use cargo_metadata::{Metadata, MetadataCommand};
use governor_owners::{PackageOwnersConfig, WorkspaceOwnersConfig};
use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Deserialize)]
struct GovernorMetadata {
owners: Option<OwnersMetadata>,
}
#[derive(Debug, Deserialize)]
struct OwnersMetadata {
#[serde(default)]
users: Vec<String>,
#[serde(default)]
groups: HashMap<String, Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct CargoConfig {
pub workspace: Option<WorkspaceOwnersConfig>,
pub packages: Vec<PackageConfig>,
}
#[derive(Debug, Clone)]
pub struct PackageConfig {
pub name: String,
pub owners: Option<PackageOwnersConfig>,
}
impl CargoConfig {
pub fn from_current_workspace() -> Result<Self> {
let metadata = MetadataCommand::new()
.no_deps()
.exec()
.map_err(|e| Error::Config(format!("Failed to read cargo metadata: {e}")))?;
Self::from_metadata(&metadata)
}
fn from_metadata(metadata: &Metadata) -> Result<Self> {
let workspace_config = metadata.workspace_metadata.as_object().and_then(|ws_meta| {
ws_meta.get("governor").and_then(|g| {
g.get("owners").and_then(|o| {
serde_json::from_value::<OwnersMetadata>(o.clone())
.ok()
.map(|owners| WorkspaceOwnersConfig {
users: owners.users,
groups: owners.groups,
})
})
})
});
let mut packages = Vec::new();
for package in metadata.workspace_packages() {
let name = package.name.clone();
let owners = parse_package_owners(package.manifest_path.as_std_path())?;
packages.push(PackageConfig { name, owners });
}
Ok(Self {
workspace: workspace_config,
packages,
})
}
#[must_use]
pub fn current_package(&self) -> Option<&PackageConfig> {
self.packages.first()
}
}
fn parse_owners_from_toml(content: &str) -> Result<Option<GovernorMetadata>> {
let value: toml::Value =
toml::from_str(content).map_err(|e| Error::Config(format!("Failed to parse TOML: {e}")))?;
let governor = value
.get("package")
.and_then(|p| p.get("metadata"))
.and_then(|m| m.get("governor"));
governor
.map(|v| {
v.clone()
.try_into()
.map_err(|e| Error::Config(format!("Failed to parse governor metadata: {e}")))
})
.transpose()
}
fn parse_package_owners(path: &Path) -> Result<Option<PackageOwnersConfig>> {
let content = std::fs::read_to_string(path)?;
let metadata = parse_owners_from_toml(&content)?;
Ok(metadata.and_then(|m| {
m.owners.map(|o| PackageOwnersConfig {
users: o.users,
groups: o.groups.into_keys().collect(),
..Default::default()
})
}))
}