1pub mod client;
10pub mod lockfile;
11pub mod ops;
12
13#[derive(Debug, thiserror::Error)]
15pub enum CliError {
16 #[error("http error: {0}")]
17 Http(#[from] reqwest::Error),
18 #[error("registry API {status}: {body}")]
19 Api { status: u16, body: String },
20 #[error("download failed ({status}) for {url}")]
24 Download { url: String, status: u16 },
25 #[error("io error: {0}")]
26 Io(#[from] std::io::Error),
27 #[error("lockfile parse: {0}")]
28 LockParse(#[from] toml::de::Error),
29 #[error("lockfile encode: {0}")]
30 LockEncode(#[from] toml::ser::Error),
31 #[error("invalid handle '{0}' (expected @namespace/slug)")]
32 BadHandle(String),
33 #[error(
34 "refusing to overwrite existing files (use --force):\n{}",
35 .0.join("\n")
36 )]
37 WouldOverwrite(Vec<String>),
38 #[error("{handle} [{target}] is not installed (no entry in agr.lock)")]
39 NotInstalled { handle: String, target: String },
40 #[error(
41 "{handle} no longer matches what was installed; refusing to delete (use --force):\n{}",
42 .details.join("\n")
43 )]
44 LocallyModified {
45 handle: String,
46 details: Vec<String>,
47 },
48 #[error("destination path '{0}' escapes the install directory")]
49 UnsafePath(String),
50}
51
52pub type Result<T> = std::result::Result<T, CliError>;
54
55pub fn parse_handle(handle: &str) -> Result<(String, String)> {
57 let h = handle.strip_prefix('@').unwrap_or(handle);
58 match h.split_once('/') {
59 Some((ns, slug)) if !ns.is_empty() && !slug.is_empty() && !slug.contains('/') => {
60 Ok((ns.to_string(), slug.to_string()))
61 }
62 _ => Err(CliError::BadHandle(handle.to_string())),
63 }
64}
65
66pub fn publish_line(v: &serde_json::Value) -> String {
79 let handle = match (
80 v["artifact"]["namespace"].as_str(),
81 v["artifact"]["slug"].as_str(),
82 ) {
83 (Some(ns), Some(slug)) => Some(format!("@{ns}/{slug}")),
84 _ => None,
85 };
86 match (handle, v["latest_version"]["version"].as_str()) {
87 (Some(h), Some(version)) => format!("published {h} @ {version}"),
88 (Some(h), None) => format!("published {h}"),
89 _ => serde_json::to_string_pretty(v).unwrap_or_default(),
90 }
91}