use crate::config::{LoadError, PerRepoProviders};
use crate::prompt::{PER_REPO_PROVIDERS_FILE, SOULS_DIR};
use crate::template::GitRunner;
use crate::workspace;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub enum Invalid {
RoleMissing {
role: String,
subject: String,
defined: String,
},
SoulMissing { role: String, subject: String },
Config(LoadError),
Governing { subject: String, source: io::Error },
}
impl std::fmt::Display for Invalid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RoleMissing {
role,
subject,
defined,
} => write!(
f,
"role {role:?} is not defined in the providers.yaml that will govern \
{subject} — defined roles: {defined}"
),
Self::SoulMissing { role, subject } => write!(
f,
"role {role:?} is defined but its soul {SOULS_DIR}/{role}.md is missing from \
the config that will govern {subject} — a role is its `roles:` entry and \
its soul (ARCH §4.3)"
),
Self::Config(e) => write!(f, "providers.yaml: {e}"),
Self::Governing { subject, source } => {
write!(f, "governing config for {subject}: {source}")
}
}
}
}
impl std::error::Error for Invalid {}
pub fn validate(
repo: &Path,
branch: &str,
fork_point: Option<&str>,
role: &str,
git: &dyn GitRunner,
) -> Result<(), Invalid> {
let subject = format!("a child of agent {branch:?}");
let branch_ref = workspace::agent_ref(branch);
let start = fork_point.unwrap_or(&branch_ref);
let commit = workspace::current_config::current_config(repo, start, git)
.map_err(|source| Invalid::Governing {
subject: subject.clone(),
source,
})?
.commit()
.to_string();
against(repo, &commit, &subject, role, git)
}
pub fn against(
repo: &Path,
commit: &str,
subject: &str,
role: &str,
git: &dyn GitRunner,
) -> Result<(), Invalid> {
let providers_raw = workspace::show_control(repo, commit, PER_REPO_PROVIDERS_FILE, git)
.map_err(|source| Invalid::Governing {
subject: subject.to_string(),
source,
})?;
let origin = PathBuf::from(format!("{commit}:{PER_REPO_PROVIDERS_FILE}"));
let providers = PerRepoProviders::parse(&providers_raw, &origin).map_err(Invalid::Config)?;
if !providers.roles.contains_key(role) {
let defined: Vec<&str> = providers.roles.keys().map(String::as_str).collect();
return Err(Invalid::RoleMissing {
role: role.to_string(),
subject: subject.to_string(),
defined: crate::name::pool(&defined),
});
}
let soul_rel = format!("{SOULS_DIR}/{role}.md");
if !workspace::control_exists(repo, commit, &soul_rel, git) {
return Err(Invalid::SoulMissing {
role: role.to_string(),
subject: subject.to_string(),
});
}
Ok(())
}
#[cfg(test)]
mod tests;