use crate::ISSUE_STATUS_VALUES_NEXT_STEP;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum CliError {
#[error("unknown or missing command")]
UnknownCommand,
#[error("unknown command: {command}")]
UnknownCommandName {
command: String,
next: &'static str,
},
#[error("missing argument: {argument}")]
MissingArgument {
argument: &'static str,
next: &'static str,
},
#[error("missing value for {flag}")]
MissingFlagValue {
flag: &'static str,
next: &'static str,
},
#[error("duplicate flag: {flag}")]
DuplicateFlag {
flag: &'static str,
next: &'static str,
},
#[error("unexpected argument for {command}: {argument}")]
UnexpectedArgument {
argument: String,
command: &'static str,
next: &'static str,
},
#[error("unknown flag: {flag}")]
UnknownFlag {
flag: String,
next: &'static str,
},
#[error("unsupported flag for {command}: {flag}")]
UnsupportedFlag {
flag: String,
command: &'static str,
next: &'static str,
},
#[error("unknown resource: {resource}")]
UnknownResource {
resource: String,
next: &'static str,
},
#[error("unknown issue status: {0}")]
UnknownStatus(String),
#[error("unknown log level: {0}")]
UnknownLogLevel(String),
#[error("invalid limit: {0}")]
InvalidLimit(String),
}
#[derive(Debug, thiserror::Error)]
pub enum RuntimeError {
#[error(transparent)]
Cli(#[from] CliError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Http(#[from] reqwest::Error),
#[error("not logged in: run logbrew login")]
MissingToken,
#[error("api returned status {status}: {body}")]
Api {
status: u16,
body: String,
auth_source: &'static str,
auth_label: &'static str,
},
#[error("LogBrew API unreachable: {message}")]
StatusUnavailable {
api_url: String,
status_code: Option<u16>,
body: Option<String>,
authenticated: bool,
auth_source: &'static str,
auth_label: &'static str,
message: String,
},
#[error("{message}")]
Unavailable {
message: &'static str,
next: &'static str,
},
}
pub fn write_cli_error<W: std::io::Write>(
error: &CliError,
json: bool,
output: &mut W,
) -> Result<(), std::io::Error> {
if json {
let body = serde_json::json!({
"ok": false,
"error": cli_error_code(error),
"message": error.to_string(),
"next": cli_error_next_step(error),
});
writeln!(output, "{body}")
} else {
writeln!(output, "{error}")?;
writeln!(output, "Next: {}", cli_error_next_step(error))
}
}
pub fn write_runtime_error<W: std::io::Write>(
error: &RuntimeError,
json: bool,
output: &mut W,
) -> Result<(), std::io::Error> {
if !json {
if let RuntimeError::StatusUnavailable {
api_url,
status_code,
body,
auth_label,
message,
..
} = error
{
writeln!(output, "LogBrew API unreachable.")?;
writeln!(output, "API: {api_url}")?;
writeln!(output, "Auth: {auth_label}")?;
if let Some(status_code) = status_code {
writeln!(output, "Status: {status_code}")?;
}
if let Some(body) = body.as_ref().filter(|body| !body.is_empty()) {
writeln!(output, "Body: {body}")?;
} else {
writeln!(output, "Reason: {message}")?;
}
writeln!(output, "Next: {STATUS_UNAVAILABLE_NEXT_STEP}")?;
return Ok(());
}
writeln!(output, "{error}")?;
if let RuntimeError::Api { auth_label, .. } = error {
writeln!(output, "Auth: {auth_label}")?;
}
writeln!(output, "Next: {}", runtime_error_next_step(error))?;
return Ok(());
}
let body = match error {
RuntimeError::MissingToken
| RuntimeError::Unavailable { .. }
| RuntimeError::Io(_)
| RuntimeError::Http(_) => serde_json::json!({
"ok": false,
"error": runtime_error_code(error),
"message": error.to_string(),
"next": runtime_error_next_step(error),
}),
RuntimeError::Api {
status,
body,
auth_source,
..
} => serde_json::json!({
"ok": false,
"error": runtime_error_code(error),
"message": error.to_string(),
"status": status,
"body": body,
"auth_source": auth_source,
"next": runtime_error_next_step(error),
}),
RuntimeError::StatusUnavailable {
api_url,
status_code,
body,
authenticated,
auth_source,
message,
..
} => serde_json::json!({
"ok": false,
"error": runtime_error_code(error),
"status": "unreachable",
"status_code": status_code,
"body": body,
"api_url": api_url,
"authenticated": authenticated,
"auth_source": auth_source,
"message": message,
"next": runtime_error_next_step(error),
}),
RuntimeError::Cli(error) => serde_json::json!({
"ok": false,
"error": cli_error_code(error),
"message": error.to_string(),
"next": cli_error_next_step(error),
}),
};
writeln!(output, "{body}")
}
const fn cli_error_code(error: &CliError) -> &'static str {
match error {
CliError::UnknownCommand | CliError::UnknownCommandName { .. } => "unknown_command",
CliError::MissingArgument { .. } => "missing_argument",
CliError::MissingFlagValue { .. } => "missing_flag_value",
CliError::DuplicateFlag { .. } => "duplicate_flag",
CliError::UnexpectedArgument { .. } => "unexpected_argument",
CliError::UnknownFlag { .. } => "unknown_flag",
CliError::UnsupportedFlag { .. } => "unsupported_flag",
CliError::UnknownResource { .. } => "unknown_resource",
CliError::UnknownStatus(_) => "unknown_status",
CliError::UnknownLogLevel(_) => "unknown_log_level",
CliError::InvalidLimit(_) => "invalid_limit",
}
}
const fn cli_error_next_step(error: &CliError) -> &'static str {
match error {
CliError::InvalidLimit(_) => "use --limit with a positive whole number",
CliError::MissingArgument { next, .. }
| CliError::MissingFlagValue { next, .. }
| CliError::DuplicateFlag { next, .. }
| CliError::UnexpectedArgument { next, .. }
| CliError::UnsupportedFlag { next, .. }
| CliError::UnknownFlag { next, .. }
| CliError::UnknownResource { next, .. }
| CliError::UnknownCommandName { next, .. } => next,
CliError::UnknownCommand => "run logbrew --help",
CliError::UnknownStatus(_) => ISSUE_STATUS_VALUES_NEXT_STEP,
CliError::UnknownLogLevel(_) => "use one of trace, debug, info, warn, error, fatal",
}
}
const fn runtime_error_code(error: &RuntimeError) -> &'static str {
match error {
RuntimeError::Cli(error) => cli_error_code(error),
RuntimeError::Io(_) => "io_error",
RuntimeError::Http(_) => "http_error",
RuntimeError::MissingToken => "not_logged_in",
RuntimeError::Api { .. } => "api_error",
RuntimeError::StatusUnavailable { .. } => "status_unreachable",
RuntimeError::Unavailable { .. } => "unavailable",
}
}
const STATUS_UNAVAILABLE_NEXT_STEP: &str = "check LOGBREW_API_URL or network";
const fn runtime_error_next_step(error: &RuntimeError) -> &'static str {
match error {
RuntimeError::Cli(error) => cli_error_next_step(error),
RuntimeError::MissingToken
| RuntimeError::Api {
status: 401 | 403, ..
} => "run logbrew login",
RuntimeError::Api {
status: 400 | 422, ..
} => "check command arguments or filters",
RuntimeError::Api { status: 404, .. } => "check the resource id or filters",
RuntimeError::Api { status: 429, .. } => "retry later",
RuntimeError::Api {
status: 500..=599, ..
} => "check LOGBREW_API_URL or retry later",
RuntimeError::Api { .. } => "check command arguments or retry later",
RuntimeError::StatusUnavailable { .. } | RuntimeError::Http(_) => {
STATUS_UNAVAILABLE_NEXT_STEP
}
RuntimeError::Unavailable { next, .. } => next,
RuntimeError::Io(_) => "check local files and permissions",
}
}