pub mod client;
pub mod lockfile;
pub mod ops;
#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
#[error("registry API {status}: {body}")]
Api { status: u16, body: String },
#[error("download failed ({status}) for {url}")]
Download { url: String, status: u16 },
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("lockfile parse: {0}")]
LockParse(#[from] toml::de::Error),
#[error("lockfile encode: {0}")]
LockEncode(#[from] toml::ser::Error),
#[error("invalid handle '{0}' (expected @namespace/slug)")]
BadHandle(String),
#[error(
"refusing to overwrite existing files (use --force):\n{}",
.0.join("\n")
)]
WouldOverwrite(Vec<String>),
#[error("{handle} [{target}] is not installed (no entry in agr.lock)")]
NotInstalled { handle: String, target: String },
#[error(
"{handle} no longer matches what was installed; refusing to delete (use --force):\n{}",
.details.join("\n")
)]
LocallyModified {
handle: String,
details: Vec<String>,
},
#[error("destination path '{0}' escapes the install directory")]
UnsafePath(String),
}
pub type Result<T> = std::result::Result<T, CliError>;
pub fn parse_handle(handle: &str) -> Result<(String, String)> {
let h = handle.strip_prefix('@').unwrap_or(handle);
match h.split_once('/') {
Some((ns, slug)) if !ns.is_empty() && !slug.is_empty() && !slug.contains('/') => {
Ok((ns.to_string(), slug.to_string()))
}
_ => Err(CliError::BadHandle(handle.to_string())),
}
}
pub fn publish_line(v: &serde_json::Value) -> String {
let handle = match (
v["artifact"]["namespace"].as_str(),
v["artifact"]["slug"].as_str(),
) {
(Some(ns), Some(slug)) => Some(format!("@{ns}/{slug}")),
_ => None,
};
match (handle, v["latest_version"]["version"].as_str()) {
(Some(h), Some(version)) => format!("published {h} @ {version}"),
(Some(h), None) => format!("published {h}"),
_ => serde_json::to_string_pretty(v).unwrap_or_default(),
}
}