Skip to content

Commit 9c1e7d1

Browse files
Create specific exit error codes (#300)
## Usage and product changes We generate specific error codes to check when executing commands and scripts programmatically. We now have the following exit codes: ``` Success = 0, GeneralError = 1, CommandError = 2, ConnectionError = 3, UserInputError = 4, QueryError = 5, ```
1 parent c7bdc17 commit 9c1e7d1

File tree

1 file changed

+73
-31
lines changed

1 file changed

+73
-31
lines changed

src/main.rs

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ const TRANSACTION_REPL_HISTORY: &'static str = "typedb_console_transaction_repl_
5757
const DIAGNOSTICS_REPORTING_URI: &'static str =
5858
"https://7f0ccb67b03abfccbacd7369d1f4ac6b@o4506315929812992.ingest.sentry.io/4506355433537536";
5959

60+
#[derive(Debug, Copy, Clone)]
61+
enum ExitCode {
62+
Success = 0,
63+
GeneralError = 1,
64+
CommandError = 2,
65+
ConnectionError = 3,
66+
UserInputError = 4,
67+
QueryError = 5,
68+
}
69+
70+
fn exit_with_error(err: &(dyn std::error::Error + 'static)) -> ! {
71+
use crate::repl::command::ReplError;
72+
if let Some(repl_err) = err.downcast_ref::<ReplError>() {
73+
eprintln!("Error: {}", repl_err);
74+
exit(ExitCode::UserInputError as i32);
75+
} else if let Some(io_err) = err.downcast_ref::<io::Error>() {
76+
eprintln!("I/O Error: {}", io_err);
77+
exit(ExitCode::GeneralError as i32);
78+
} else if let Some(driver_err) = err.downcast_ref::<typedb_driver::Error>() {
79+
eprintln!("TypeDB Error: {}", driver_err);
80+
exit(ExitCode::QueryError as i32);
81+
} else if let Some(command_error) = err.downcast_ref::<CommandError>() {
82+
eprintln!("Command Error: {}", command_error);
83+
exit(ExitCode::CommandError as i32);
84+
} else {
85+
eprintln!("Error: {}", err);
86+
exit(ExitCode::GeneralError as i32);
87+
}
88+
}
89+
6090
struct ConsoleContext {
6191
invocation_dir: PathBuf,
6292
repl_stack: Vec<Rc<Repl<ConsoleContext>>>,
@@ -94,7 +124,7 @@ fn main() {
94124
let mut args = Args::parse();
95125
if args.version {
96126
println!("{}", VERSION);
97-
exit(0);
127+
exit(ExitCode::Success as i32);
98128
}
99129
if args.password.is_none() {
100130
args.password = Some(LineReaderHidden::new().readline(&format!("password for '{}': ", args.username)));
@@ -103,13 +133,13 @@ fn main() {
103133
init_diagnostics()
104134
}
105135
if !args.tls_disabled && !args.address.starts_with("https:") {
106-
println!(
136+
eprintln!(
107137
"\
108138
TLS connections can only be enabled when connecting to HTTPS endpoints, for example using 'https://<ip>:port'. \
109139
Please modify the address, or disable TLS (--tls-disabled). WARNING: this will send passwords over plaintext!\
110140
"
111141
);
112-
exit(1);
142+
exit(ExitCode::UserInputError as i32);
113143
}
114144
let runtime = BackgroundRuntime::new();
115145
let tls_root_ca_path = args.tls_root_ca.as_ref().map(|value| Path::new(value));
@@ -120,11 +150,11 @@ fn main() {
120150
)) {
121151
Ok(driver) => Arc::new(driver),
122152
Err(err) => {
123-
println!("Failed to create driver connection to server. {}", err);
153+
eprintln!("Failed to create driver connection to server. {}", err);
124154
if !args.tls_disabled {
125-
println!("Verify that the server is also configured with TLS encryption.");
155+
eprintln!("Verify that the server is also configured with TLS encryption.");
126156
}
127-
exit(1);
157+
exit(ExitCode::ConnectionError as i32);
128158
}
129159
};
130160

@@ -140,27 +170,34 @@ fn main() {
140170
};
141171

142172
if !args.command.is_empty() && !args.script.is_empty() {
143-
println!("Error: Cannot specify both commands and files");
144-
exit(1);
173+
eprintln!("Error: Cannot specify both commands and files");
174+
exit(ExitCode::UserInputError as i32);
145175
} else if !args.command.is_empty() {
146-
execute_command_list(&mut context, &args.command);
176+
if let Err(err) = execute_command_list(&mut context, &args.command) {
177+
exit_with_error(&*err);
178+
}
147179
} else if !args.script.is_empty() {
148-
execute_scripts(&mut context, &args.script);
180+
if let Err(err) = execute_scripts(&mut context, &args.script) {
181+
exit_with_error(&*err);
182+
}
149183
} else {
150184
execute_interactive(&mut context);
151185
}
152186
}
153187

154-
fn execute_scripts(context: &mut ConsoleContext, files: &[String]) {
188+
fn execute_scripts(context: &mut ConsoleContext, files: &[String]) -> Result<(), Box<dyn Error>> {
155189
for file_path in files {
156190
let path = context.convert_path(file_path);
157191
if let Ok(file) = File::open(&file_path) {
158192
execute_script(context, path, io::BufReader::new(file).lines())
159193
} else {
160-
println!("Error opening file: {}", path.to_string_lossy());
161-
exit(1);
194+
return Err(Box::new(io::Error::new(
195+
io::ErrorKind::NotFound,
196+
format!("Error opening file: {}", path.to_string_lossy()),
197+
)));
162198
}
163199
}
200+
Ok(())
164201
}
165202

166203
fn execute_script(
@@ -187,13 +224,14 @@ fn execute_script(
187224
context.script_dir = None;
188225
}
189226

190-
fn execute_command_list(context: &mut ConsoleContext, commands: &[String]) {
227+
fn execute_command_list(context: &mut ConsoleContext, commands: &[String]) -> Result<(), Box<dyn Error>> {
191228
for command in commands {
192-
if let Err(_) = execute_commands(context, command, true, true) {
193-
println!("### Stopped executing at command: {}", command);
194-
exit(1);
229+
if let Err(err) = execute_commands(context, command, true, true) {
230+
eprintln!("### Stopped executing at command: {}", command);
231+
return Err(Box::new(err));
195232
}
196233
}
234+
Ok(())
197235
}
198236

199237
fn execute_interactive(context: &mut ConsoleContext) {
@@ -221,7 +259,7 @@ fn execute_interactive(context: &mut ConsoleContext) {
221259
// do nothing
222260
} else {
223261
// this is unexpected... quit
224-
exit(1)
262+
exit(ExitCode::GeneralError as i32);
225263
}
226264
}
227265
Err(err) => {
@@ -236,16 +274,17 @@ fn execute_commands(
236274
mut input: &str,
237275
coerce_each_command_to_one_line: bool,
238276
must_log_command: bool,
239-
) -> Result<(), EmptyError> {
277+
) -> Result<(), CommandError> {
240278
let mut multiple_commands = None;
241279
while !context.repl_stack.is_empty() && !input.trim().is_empty() {
242280
let repl_index = context.repl_stack.len() - 1;
243281
let current_repl = context.repl_stack[repl_index].clone();
244282

245283
input = match current_repl.match_first_command(input, coerce_each_command_to_one_line) {
246284
Ok(None) => {
247-
println!("Unrecognised command: {}", input);
248-
return Err(EmptyError {});
285+
let message = format!("Unrecognised command: {}", input);
286+
eprintln!("{}", message);
287+
return Err(CommandError { message });
249288
}
250289
Ok(Some((command, arguments, next_command_index))) => {
251290
let command_string = &input[0..next_command_index];
@@ -254,19 +293,20 @@ fn execute_commands(
254293
}
255294

256295
if must_log_command || multiple_commands.is_some_and(|b| b) {
257-
println!("{} {}", "+".repeat(repl_index + 1), command_string.trim());
296+
eprintln!("{} {}", "+".repeat(repl_index + 1), command_string.trim());
258297
}
259298
match command.execute(context, arguments) {
260299
Ok(_) => &input[next_command_index..],
261300
Err(err) => {
262-
println!("Error executing command: '{}'\n{}", command_string.trim(), err);
263-
return Err(EmptyError {});
301+
let message = format!("Error executing command: '{}'\n{}", command_string.trim(), err);
302+
eprintln!("{}", message);
303+
return Err(CommandError { message });
264304
}
265305
}
266306
}
267307
Err(err) => {
268-
println!("{}", err);
269-
return Err(EmptyError {});
308+
eprintln!("{}", err);
309+
return Err(CommandError { message: err.to_string() });
270310
}
271311
};
272312
input = input.trim_start();
@@ -432,18 +472,20 @@ fn init_diagnostics() {
432472
));
433473
}
434474

435-
struct EmptyError {}
475+
struct CommandError {
476+
message: String,
477+
}
436478

437-
impl Debug for EmptyError {
479+
impl Debug for CommandError {
438480
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
439481
Display::fmt(self, f)
440482
}
441483
}
442484

443-
impl Display for EmptyError {
485+
impl Display for CommandError {
444486
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
445-
write!(f, "")
487+
write!(f, "{}", self.message)
446488
}
447489
}
448490

449-
impl Error for EmptyError {}
491+
impl Error for CommandError {}

0 commit comments

Comments
 (0)