1pub mod backup;
2pub mod list;
3pub mod manifest;
4pub mod restore;
5pub mod snapshot;
6
7use std::ffi::OsString;
8use thiserror::Error as ThisError;
9
10#[derive(Debug, ThisError)]
15pub enum CliError {
16 #[error("{0}")]
17 Usage(&'static str),
18
19 #[error(transparent)]
20 Backup(#[from] backup::BackupCommandError),
21
22 #[error(transparent)]
23 List(#[from] list::ListCommandError),
24
25 #[error(transparent)]
26 Manifest(#[from] manifest::ManifestCommandError),
27
28 #[error(transparent)]
29 Snapshot(#[from] snapshot::SnapshotCommandError),
30
31 #[error(transparent)]
32 Restore(#[from] restore::RestoreCommandError),
33}
34
35pub fn run_from_env() -> Result<(), CliError> {
37 run(std::env::args_os().skip(1))
38}
39
40pub fn run<I>(args: I) -> Result<(), CliError>
42where
43 I: IntoIterator<Item = OsString>,
44{
45 let mut args = args.into_iter();
46 let Some(command) = args.next().and_then(|arg| arg.into_string().ok()) else {
47 return Err(CliError::Usage(usage()));
48 };
49
50 match command.as_str() {
51 "backup" => backup::run(args).map_err(CliError::from),
52 "list" => list::run(args).map_err(CliError::from),
53 "manifest" => manifest::run(args).map_err(CliError::from),
54 "snapshot" => snapshot::run(args).map_err(CliError::from),
55 "restore" => restore::run(args).map_err(CliError::from),
56 "help" | "--help" | "-h" => {
57 println!("{}", usage());
58 Ok(())
59 }
60 _ => Err(CliError::Usage(usage())),
61 }
62}
63
64const fn usage() -> &'static str {
66 "usage: canic <command> [<args>]\n\ncommands:\n list Show registry canisters as an ASCII tree.\n snapshot Capture and download canister snapshots.\n backup Inspect, verify, preflight, or smoke-check a backup directory.\n manifest Validate fleet backup manifests.\n restore Plan, preview, summarize, or run restore journals.\n\nhelp:\n canic help\n canic <command> help"
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
75 fn usage_lists_command_families_without_nested_flags() {
76 let text = usage();
77
78 assert!(text.contains("usage: canic <command> [<args>]"));
79 assert!(text.contains("list"));
80 assert!(text.contains("snapshot"));
81 assert!(text.contains("backup"));
82 assert!(text.contains("manifest"));
83 assert!(text.contains("restore"));
84 assert!(text.contains("canic <command> help"));
85 assert!(!text.contains("--require-batch-ready-delta"));
86 assert!(!text.contains("--require-no-pending-before"));
87 }
88
89 #[test]
91 fn command_family_help_returns_ok() {
92 assert!(run([OsString::from("backup"), OsString::from("help")]).is_ok());
93 assert!(run([OsString::from("list"), OsString::from("help")]).is_ok());
94 assert!(run([OsString::from("restore"), OsString::from("help")]).is_ok());
95 assert!(run([OsString::from("manifest"), OsString::from("help")]).is_ok());
96 assert!(run([OsString::from("snapshot"), OsString::from("help")]).is_ok());
97 }
98}