1mod apps;
2mod auth;
3mod backup;
4mod blob_storage;
5mod build;
6mod cli;
7mod cycles;
8mod deploy;
9mod endpoints;
10mod evidence;
11mod evidence_support;
12mod info;
13mod info_env;
14mod info_subnets;
15mod inspect;
16mod install;
17mod list;
18mod medic;
19mod metrics;
20mod network;
21mod output;
22mod replica;
23mod restore;
24mod scaffold;
25mod state;
26mod status;
27mod support;
28#[cfg(test)]
29mod test_support;
30mod token;
31
32use crate::cli::{
33 clap::parse_matches,
34 globals::{
35 DISPATCH_ARGS, apply_global_environment, apply_global_icp, command_local_global_option,
36 top_level_dispatch_command,
37 },
38 help::{first_arg_is_help, usage},
39};
40pub use cli::top_level_command;
41use std::ffi::OsString;
42use thiserror::Error as ThisError;
43
44const VERSION_TEXT: &str = concat!("canic ", env!("CARGO_PKG_VERSION"));
45
46#[derive(Debug, ThisError)]
51pub enum CliError {
52 #[error("{0}")]
53 Usage(String),
54
55 #[error("backup: {0}")]
56 Backup(#[from] backup::BackupCommandError),
57
58 #[error("auth: {0}")]
59 Auth(#[from] auth::AuthCommandError),
60
61 #[error("blob-storage: {0}")]
62 BlobStorage(#[from] blob_storage::BlobStorageCommandError),
63
64 #[error("build: {0}")]
65 Build(#[from] build::BuildCommandError),
66
67 #[error("cycles: {0}")]
68 Cycles(#[from] cycles::CyclesCommandError),
69
70 #[error("deploy: {0}")]
71 Deploy(#[from] deploy::DeployCommandError),
72
73 #[error("evidence: {0}")]
74 Evidence(#[from] evidence::EvidenceCommandError),
75
76 #[error("install: {0}")]
77 Install(#[from] install::InstallCommandError),
78
79 #[error("inspect: {0}")]
80 Inspect(#[from] inspect::InspectCommandError),
81
82 #[error("info: {0}")]
83 Info(#[from] info::InfoCommandError),
84
85 #[error("medic: {0}")]
86 Medic(#[from] medic::MedicCommandError),
87
88 #[error("network: {0}")]
89 Network(#[from] network::NetworkCommandError),
90
91 #[error("app: {0}")]
92 Apps(#[from] apps::AppCommandError),
93
94 #[error("state: {0}")]
95 State(#[from] state::StateCommandError),
96
97 #[error("scaffold: {0}")]
98 Scaffold(#[from] scaffold::ScaffoldCommandError),
99
100 #[error("restore: {0}")]
101 Restore(#[from] restore::RestoreCommandError),
102
103 #[error("replica: {0}")]
104 Replica(#[from] replica::ReplicaCommandError),
105
106 #[error("status: {0}")]
107 Status(#[from] status::StatusCommandError),
108
109 #[error("token: {0}")]
110 Token(#[from] token::TokenCommandError),
111}
112
113pub fn run_from_env() -> Result<(), CliError> {
115 run(std::env::args_os().skip(1))
116}
117
118pub fn run<I>(args: I) -> Result<(), CliError>
120where
121 I: IntoIterator<Item = OsString>,
122{
123 let args = args.into_iter().collect::<Vec<_>>();
124 if first_arg_is_help(&args) {
125 println!("{}", usage());
126 return Ok(());
127 }
128 if let Some(option) = command_local_global_option(&args) {
129 return Err(CliError::Usage(format!(
130 "{option} is a top-level option; put it before the command\n\n{}",
131 usage()
132 )));
133 }
134
135 let matches =
136 parse_matches(top_level_dispatch_command(), args).map_err(|_| CliError::Usage(usage()))?;
137 if matches.get_flag("version") {
138 println!("{}", version_text());
139 return Ok(());
140 }
141 let global_icp = cli::clap::string_option(&matches, "icp");
142 let global_environment = cli::clap::string_option(&matches, "environment");
143
144 let Some((command, subcommand_matches)) = matches.subcommand() else {
145 return Err(CliError::Usage(usage()));
146 };
147 let mut tail = subcommand_matches
148 .get_many::<OsString>(DISPATCH_ARGS)
149 .map(|values| values.cloned().collect::<Vec<_>>())
150 .unwrap_or_default();
151 apply_global_icp(command, &mut tail, global_icp);
152 apply_global_environment(command, &mut tail, global_environment);
153 let tail = tail.into_iter();
154
155 match command {
156 "backup" => backup::run(tail).map_err(CliError::from),
157 "auth" => auth::run(tail).map_err(CliError::from),
158 "blob-storage" => blob_storage::run(tail).map_err(CliError::from),
159 "build" => build::run(tail).map_err(CliError::from),
160 "cycles" => cycles::run(tail).map_err(CliError::from),
161 "deploy" => deploy::run(tail).map_err(CliError::from),
162 "evidence" => evidence::run(tail).map_err(CliError::from),
163 "app" => apps::run(tail).map_err(CliError::from),
164 "info" => info::run(tail).map_err(CliError::from),
165 "install" => install::run(tail).map_err(CliError::from),
166 "inspect" => inspect::run(tail).map_err(CliError::from),
167 "medic" => medic::run(tail).map_err(CliError::from),
168 "network" => network::run(tail).map_err(CliError::from),
169 "replica" => replica::run(tail).map_err(CliError::from),
170 "scaffold" => scaffold::run(tail).map_err(CliError::from),
171 "state" => state::run(tail).map_err(CliError::from),
172 "status" => status::run(tail).map_err(CliError::from),
173 "token" => token::run(tail).map_err(CliError::from),
174 "restore" => restore::run(tail).map_err(CliError::from),
175 _ => unreachable!("top-level dispatch command only defines known commands"),
176 }
177}
178
179#[must_use]
180pub const fn version_text() -> &'static str {
181 VERSION_TEXT
182}
183
184#[must_use]
185pub fn render_cli_error(error: &CliError) -> String {
186 match error {
187 CliError::BlobStorage(err) => err.json_error_report().unwrap_or_else(|| error.to_string()),
188 CliError::Deploy(err) if err.suppress_stderr() => String::new(),
189 CliError::Inspect(err) if err.suppress_stderr() => String::new(),
190 CliError::Medic(err) if err.suppress_stderr() => String::new(),
191 CliError::State(err) if err.suppress_stderr() => String::new(),
192 _ => error.to_string(),
193 }
194}
195
196#[must_use]
197pub fn cli_error_exit_code(err: &CliError) -> i32 {
198 match err {
199 CliError::Usage(_) => 2,
200 CliError::Auth(err) => i32::from(err.exit_code()),
201 CliError::BlobStorage(err) => i32::from(err.exit_code()),
202 CliError::Deploy(err) => i32::from(err.exit_code()),
203 CliError::Info(err) => i32::from(err.exit_code()),
204 CliError::Inspect(err) => i32::from(err.exit_code()),
205 CliError::Medic(err) => i32::from(err.exit_code()),
206 CliError::State(err) => i32::from(err.exit_code()),
207 _ => 1,
208 }
209}
210
211#[cfg(test)]
212mod tests;