1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* Copyright © 2020-2021 Jeremy Carter <jeremy@jeremycarter.ca>

By using this software, you agree to the LICENSE TERMS 
outlined in the file titled LICENSE.md contained in the 
top-level directory of this project. If you don't agree
to the LICENSE TERMS, you aren't allowed to use this
software.
*/

/*! Handle errors in a common way for everything in this 
crate. This is mostly just wrapping the
[clap](https://docs.rs/clap/latest/clap) crate's 
[Error](https://docs.rs/clap/latest/clap/struct.Error.html) 
type, and adding numeric exit codes to each of their types 
of errors which are suitable for returning to the parent 
shell or execution environment, to support properly checking 
various error conditions for advanced scripting purposes 
(bash scripts, Windows batch files, or Powershell scripts, 
for example).
*/

use clap;
use clap::ErrorKind;
use clap::ErrorKind::*;
use std::fmt;

/** Get an exit code for each kind of error. This
code is suitable for passing back to the parent
shell or execution environment as an exit value,
to facilitate proper error checking when using
this library's functions with advanced scripting,
such as with a bash script, Windows batch file,
or PowerShell script.

"kind" parameter, accepts any clap::ErrorKind value, and then the function returns the associated number:
```ignore
InvalidValue => 1
UnknownArgument => 2
InvalidSubcommand => 3
UnrecognizedSubcommand => 4
EmptyValue => 5
ValueValidation => 6
TooManyValues => 7
TooFewValues => 8
WrongNumberOfValues => 9
ArgumentConflict => 10
MissingRequiredArgument => 11
MissingSubcommand => 12
MissingArgumentOrSubcommand => 13
UnexpectedMultipleUsage => 14
InvalidUtf8 => 15
HelpDisplayed => 16
VersionDisplayed => 17
ArgumentNotFound => 18
Io => 19
Format => 20
```
*/
pub fn get_code(kind: ErrorKind) -> i32 {
	match kind {
		InvalidValue => 1,
		UnknownArgument => 2,
		InvalidSubcommand => 3,
		UnrecognizedSubcommand => 4,
		EmptyValue => 5,
		ValueValidation => 6,
		TooManyValues => 7,
		TooFewValues => 8,
		WrongNumberOfValues => 9,
		ArgumentConflict => 10,
		MissingRequiredArgument => 11,
		MissingSubcommand => 12,
		MissingArgumentOrSubcommand => 13,
		UnexpectedMultipleUsage => 14,
		InvalidUtf8 => 15,
		HelpDisplayed => 16,
		VersionDisplayed => 17,
		ArgumentNotFound => 18,
		Io => 19,
		Format => 20,
	}
}

/** Handle the return value of a function from the `command` module,
suppressing any non-important types of errors.
*/
pub fn handle_exit(res: Result<i32, Error>) -> Result<i32, Error> {
	let suppress_errors = [HelpDisplayed, VersionDisplayed, MissingArgumentOrSubcommand];

	res.map_err(|e| {
		for suppress in suppress_errors.iter().cloned() {
			if e.kind == suppress {
				println!("{}", e.message);
				return e;
			}
		}

		println!("\nexiting with error: {}\nexit code:\n{}", e, e.code);
		e
	})
}

/** A data type which wraps the
[clap](https://docs.rs/clap/latest/clap) crate's 
[Error](https://docs.rs/clap/latest/clap/struct.Error.html) 
type, and adds numeric exit codes to each of their types 
of errors, as well as a few extra helper functions.
*/
#[derive(Debug)]
pub struct Error {
	/// An error message.
	pub message: String,

	/// An optional set of info about the error.
	pub info: Option<Vec<String>>,

	/// The kind of error that it is.
	pub kind: ErrorKind,

	/// The numeric error code, suitable for returning to the parent shell on exit.
	pub code: i32,

	/// The printable name of the kind of error that it is.
	pub name: String,
}

impl Error {
	/// Construct a new Error value.
	pub fn new<'a>(message: &'a str, info: Option<Vec<String>>, kind: ErrorKind) -> Self {
		let message = String::from(message);
		let code = get_code(kind);
		let name = format!("{:?}", kind);

		Self {
			message,
			info,
			kind,
			code,
			name,
		}
	}
}

/// Easily construct a new Error value using one of these convenience functions.
pub trait From {
	/** Construct a new Error value from an existing
	[clap](https://docs.rs/clap/latest/clap) 
	[Error](https://docs.rs/clap/latest/clap/struct.Error.html)
	*/
	fn from_clap_error(val: clap::Error) -> Self;

	/** Construct a new Error value with a given error message, using a certain
	[clap](https://docs.rs/clap/latest/clap)::[ErrorKind](https://docs.rs/clap/latest/clap/enum.ErrorKind.html)
	as a base.
	*/
	fn with_description(message: &str, kind: clap::ErrorKind) -> Self;
}

impl From for Error {
	fn from_clap_error(e: clap::Error) -> Self {
		Error::new(&e.message, e.info, e.kind)
	}

	fn with_description(message: &str, kind: clap::ErrorKind) -> Self {
		let e = clap::Error::with_description(message, kind);
		Error::new(&e.message, e.info, e.kind)
	}
}

impl fmt::Display for Error {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		if self.info.is_some() {
			write!(
				f,
				"\nerror ({} {}): {}: {:?}\n",
				self.name,
				self.code,
				self.message,
				self.info.clone().unwrap_or_default()
			)
		} else {
			write!(
				f,
				"\nerror ({} {}): {}\n",
				self.name, self.code, self.message
			)
		}
	}
}