mirror of
https://github.com/NLnetLabs/dnst.git
synced 2026-09-27 20:24:57 +02:00
Add context tracking for errors (#18)
* [error] Introduce context tracking * [error] Add an extension trait to 'Result' * [error] Move pretty-printing to explicit function * Pretty-print errors on exit * [erro] Fix primary field documentation --------- Co-authored-by: arya dradjica <arya@nlnetlabs.nl>
This commit is contained in:
co-authored by
arya dradjica
parent
2e6d8a9199
commit
8dc82c7ff6
@@ -1,7 +1,7 @@
|
||||
use crate::error::Error;
|
||||
use clap::builder::ValueParser;
|
||||
use domain::base::iana::nsec3::Nsec3HashAlg;
|
||||
use domain::base::name::Name;
|
||||
use domain::base::name::{self, Name};
|
||||
use domain::base::ToName;
|
||||
use domain::rdata::nsec3::{Nsec3Salt, OwnerHash};
|
||||
use lexopt::Arg;
|
||||
@@ -106,11 +106,11 @@ impl LdnsCommand for Nsec3Hash {
|
||||
}
|
||||
|
||||
impl Nsec3Hash {
|
||||
pub fn parse_name(arg: &str) -> Result<Name<Vec<u8>>, Error> {
|
||||
Name::from_str(&arg.to_lowercase()).map_err(|e| Error::from(e.to_string()))
|
||||
pub fn parse_name(arg: &str) -> Result<Name<Vec<u8>>, name::FromStrError> {
|
||||
Name::from_str(&arg.to_lowercase())
|
||||
}
|
||||
|
||||
pub fn parse_nsec_alg(arg: &str) -> Result<Nsec3HashAlg, Error> {
|
||||
pub fn parse_nsec_alg(arg: &str) -> Result<Nsec3HashAlg, &'static str> {
|
||||
if let Ok(num) = arg.parse() {
|
||||
let alg = Nsec3HashAlg::from_int(num);
|
||||
// check for valid algorithm here, to be consistent with error messages
|
||||
@@ -118,11 +118,10 @@ impl Nsec3Hash {
|
||||
if alg.to_mnemonic().is_some() {
|
||||
Ok(alg)
|
||||
} else {
|
||||
Err(Error::from("unknown algorithm number"))
|
||||
Err("unknown algorithm number")
|
||||
}
|
||||
} else {
|
||||
Nsec3HashAlg::from_mnemonic(arg.as_bytes())
|
||||
.ok_or(Error::from("unknown algorithm mnemonic"))
|
||||
Nsec3HashAlg::from_mnemonic(arg.as_bytes()).ok_or("unknown algorithm mnemonic")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+103
-29
@@ -1,44 +1,81 @@
|
||||
use std::borrow::Cow;
|
||||
use std::num::ParseIntError;
|
||||
use std::string::ParseError;
|
||||
use std::{error, fmt, io};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Error {
|
||||
message: Cow<'static, str>,
|
||||
//------------ Error ---------------------------------------------------------
|
||||
|
||||
/// A program error.
|
||||
///
|
||||
/// Such errors are highly likely to halt the program.
|
||||
#[derive(Clone)]
|
||||
pub struct Error(Box<Information>);
|
||||
|
||||
/// Information about an error.
|
||||
#[derive(Clone)]
|
||||
struct Information {
|
||||
/// The primary error message.
|
||||
primary: Box<str>,
|
||||
|
||||
/// Layers of context to the error.
|
||||
///
|
||||
/// Ordered from innermost to outermost.
|
||||
context: Vec<Box<str>>,
|
||||
}
|
||||
|
||||
impl From<&'static str> for Error {
|
||||
fn from(message: &'static str) -> Self {
|
||||
Self {
|
||||
message: Cow::Borrowed(message),
|
||||
//--- Interaction
|
||||
|
||||
impl Error {
|
||||
/// Construct a new error from a string.
|
||||
pub fn new(error: &str) -> Self {
|
||||
Self(Box::new(Information {
|
||||
primary: error.into(),
|
||||
context: Vec::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Add context to this error.
|
||||
pub fn context(mut self, context: &str) -> Self {
|
||||
self.0.context.push(context.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Pretty-print this error.
|
||||
pub fn pretty_print(self) {
|
||||
use std::io::IsTerminal;
|
||||
|
||||
// NOTE: This is a multicall binary, so argv[0] is necessary for
|
||||
// program operation. We would fail very early if it didn't exist.
|
||||
let prog = std::env::args().next().unwrap();
|
||||
let term = std::io::stderr().is_terminal();
|
||||
|
||||
let error_marker = if term {
|
||||
"\x1B[31mERROR:\x1B[0m"
|
||||
} else {
|
||||
"ERROR:"
|
||||
};
|
||||
|
||||
eprint!("[{prog}] {error_marker} {}", self.0.primary);
|
||||
for context in &self.0.context {
|
||||
eprint!("\n... while {context}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--- Conversions for '?'
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(error: &str) -> Self {
|
||||
Self::new(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(message: String) -> Self {
|
||||
Self {
|
||||
message: Cow::Owned(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for Error {
|
||||
fn from(_err: ParseError) -> Self {
|
||||
Self::from("message parse error")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(_err: ParseIntError) -> Self {
|
||||
Self::from("message parse error")
|
||||
fn from(error: String) -> Self {
|
||||
Self::new(&error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::from(err.to_string())
|
||||
fn from(error: io::Error) -> Self {
|
||||
Self::new(&error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +85,47 @@ impl From<lexopt::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
//--- Display, Debug
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.message, f)
|
||||
f.write_str(&self.0.primary)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Error")
|
||||
.field("primary", &self.0.primary)
|
||||
.field("context", &self.0.context)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
//--- Error
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
//------------ Result --------------------------------------------------------
|
||||
|
||||
/// A program result.
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// An extension trait for [`Result`]s using [`Error`].
|
||||
pub trait Context: Sized {
|
||||
/// Add context for an error.
|
||||
fn context(self, context: &str) -> Self;
|
||||
|
||||
/// Add context for an error, lazily.
|
||||
fn with_context(self, context: impl FnOnce() -> String) -> Self;
|
||||
}
|
||||
|
||||
impl<T> Context for Result<T> {
|
||||
fn context(self, context: &str) -> Self {
|
||||
self.map_err(|err| err.context(context))
|
||||
}
|
||||
|
||||
fn with_context(self, context: impl FnOnce() -> String) -> Self {
|
||||
self.map_err(|err| err.context(&(context)()))
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -1,15 +1,20 @@
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use clap::Parser;
|
||||
use dnst::commands::{nsec3hash::Nsec3Hash, LdnsCommand};
|
||||
|
||||
fn main() {
|
||||
fn main() -> ExitCode {
|
||||
// If none of the ldns-* tools matched, then we continue with clap
|
||||
// argument parsing.
|
||||
let args = try_ldns_compatibility().unwrap_or_else(dnst::Args::parse);
|
||||
|
||||
if let Err(err) = args.execute() {
|
||||
eprintln!("{}", err);
|
||||
match args.execute() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
err.pretty_print();
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +30,8 @@ fn try_ldns_compatibility() -> Option<dnst::Args> {
|
||||
|
||||
match res {
|
||||
Ok(args) => Some(args),
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
Err(err) => {
|
||||
err.pretty_print();
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user