// This file is part of Moonfire NVR, a security camera network video recorder. // Copyright (C) 2018 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. // SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. use std::backtrace::Backtrace; use std::error::Error as StdError; use std::fmt::{Debug, Display}; //use std::num::NonZeroU16; pub use coded::ErrorKind; /// Like [`coded::ToErrKind`] but with more third-party implementations. /// /// It's not possible to implement those here on that trait because of the orphan rule. pub trait ToErrKind { fn err_kind(&self) -> ErrorKind; } impl ToErrKind for Error { #[inline] fn err_kind(&self) -> ErrorKind { self.0.kind } } impl ToErrKind for std::io::Error { #[inline] fn err_kind(&self) -> ErrorKind { self.kind().into() } } impl ToErrKind for rusqlite::ErrorCode { fn err_kind(&self) -> ErrorKind { use rusqlite::ErrorCode; // https://www.sqlite.org/rescode.html match self { ErrorCode::InternalMalfunction => ErrorKind::Internal, ErrorCode::PermissionDenied => ErrorKind::PermissionDenied, ErrorCode::OperationAborted => ErrorKind::Aborted, // Conflict with another database connection in a process which is accessing // the database, apparently without using Moonfire NVR's scheme of acquiring // a lock on the db directory. // https://www.sqlite.org/wal.html#sometimes_queries_return_sqlite_busy_in_wal_mode ErrorCode::DatabaseBusy => ErrorKind::Unavailable, // Conflict within the same database connection. Shouldn't happen for Moonfire. ErrorCode::DatabaseLocked => ErrorKind::Internal, ErrorCode::OutOfMemory => ErrorKind::ResourceExhausted, ErrorCode::ReadOnly => ErrorKind::FailedPrecondition, ErrorCode::OperationInterrupted => ErrorKind::Aborted, ErrorCode::SystemIoFailure => ErrorKind::Unavailable, ErrorCode::DatabaseCorrupt => ErrorKind::DataLoss, ErrorCode::NotFound => ErrorKind::NotFound, ErrorCode::DiskFull => ErrorKind::ResourceExhausted, ErrorCode::CannotOpen => ErrorKind::Unavailable, // Similar to DatabaseBusy in this implies a conflict with another conn. ErrorCode::FileLockingProtocolFailed => ErrorKind::Unavailable, // Likewise: Moonfire NVR should never change the schema // mid-statement, so the most plausible explanation for // SchemaChange is another process. ErrorCode::SchemaChanged => ErrorKind::Unavailable, ErrorCode::TooBig => ErrorKind::ResourceExhausted, ErrorCode::ConstraintViolation => ErrorKind::Internal, ErrorCode::TypeMismatch => ErrorKind::Internal, ErrorCode::ApiMisuse => ErrorKind::Internal, ErrorCode::NoLargeFileSupport => ErrorKind::ResourceExhausted, ErrorCode::AuthorizationForStatementDenied => ErrorKind::Internal, ErrorCode::ParameterOutOfRange => ErrorKind::Internal, ErrorCode::NotADatabase => ErrorKind::FailedPrecondition, _ => ErrorKind::Unknown, } } } impl ToErrKind for rusqlite::Error { #[inline] fn err_kind(&self) -> ErrorKind { match self { rusqlite::Error::SqliteFailure(e, _) => e.code.err_kind(), _ => ErrorKind::Unknown, } } } impl ToErrKind for rusqlite::types::FromSqlError { fn err_kind(&self) -> ErrorKind { match self { rusqlite::types::FromSqlError::InvalidType => ErrorKind::FailedPrecondition, rusqlite::types::FromSqlError::OutOfRange(_) => ErrorKind::OutOfRange, rusqlite::types::FromSqlError::InvalidBlobSize { .. } => ErrorKind::OutOfRange, /* rusqlite::types::FromSqlError::Other(_) | */ _ => ErrorKind::Unknown, } } } impl ToErrKind for nix::Error { fn err_kind(&self) -> ErrorKind { use nix::Error; match self { Error::EACCES | Error::EPERM => ErrorKind::PermissionDenied, Error::EDQUOT => ErrorKind::ResourceExhausted, Error::EBUSY | Error::EEXIST | Error::ENOTDIR | Error::EROFS | Error::EFBIG | Error::EOVERFLOW | Error::ENXIO | Error::ETXTBSY => ErrorKind::FailedPrecondition, Error::EINVAL | Error::ENAMETOOLONG => ErrorKind::InvalidArgument, Error::ELOOP => ErrorKind::FailedPrecondition, Error::EMLINK | Error::ENOMEM | Error::ENOSPC | Error::EMFILE | Error::ENFILE => { ErrorKind::ResourceExhausted } Error::EBADF | Error::EFAULT => ErrorKind::InvalidArgument, Error::EINTR | Error::EAGAIN => ErrorKind::Aborted, Error::ENOENT | Error::ENODEV => ErrorKind::NotFound, Error::EOPNOTSUPP => ErrorKind::Unimplemented, _ => ErrorKind::Unknown, } } } pub struct Error(Box); struct ErrorInner { kind: ErrorKind, msg: Option, //http_status: Option, backtrace: Option, source: Option>, } pub struct ErrorBuilder(Box); impl Default for ErrorBuilder { #[inline] fn default() -> Self { Self(Box::new(ErrorInner { kind: ErrorKind::Unknown, msg: None, // http_status: None, backtrace: None, source: None, })) } } impl From for ErrorBuilder { #[inline] fn from(value: ErrorKind) -> Self { Self::default().kind(value) } } impl ErrorBuilder { #[inline] pub fn kind(mut self, kind: ErrorKind) -> Self { self.0.kind = kind; self } #[inline] pub fn map ErrorKind>(mut self, f: F) -> Self { self.0.kind = f(self.0.kind); self } #[inline] pub fn msg(mut self, msg: String) -> Self { self.0.msg = Some(msg); self } #[inline] pub fn source>>(mut self, source: S) -> Self { self.0.source = Some(source.into()); self } #[inline] pub fn build(self) -> Error { Error(self.0) } } macro_rules! cvt { ($t:ty) => { impl From<$t> for ErrorBuilder { #[inline] fn from(t: $t) -> Self { Self::default().kind(ToErrKind::err_kind(&t)).source(t) } } impl From<$t> for Error { #[inline(always)] fn from(t: $t) -> Self { Self($crate::ErrorBuilder::from(t).0) } } }; } cvt!(rusqlite::Error); cvt!(rusqlite::types::FromSqlError); cvt!(std::io::Error); cvt!(nix::Error); impl From for ErrorBuilder { #[inline] fn from(value: Error) -> Self { Self::default() .kind(ToErrKind::err_kind(&value)) .source(value) } } /// Captures a backtrace if enabled for the given error kind. // TODO: make this more configurable at runtime. fn maybe_backtrace(kind: ErrorKind) -> Option { if matches!(kind, ErrorKind::Internal | ErrorKind::Unknown) { Some(Backtrace::capture()) } else { None } } impl Error { #[inline] pub fn wrap(kind: ErrorKind, e: E) -> Self { Self(Box::new(ErrorInner { kind, msg: None, // http_status: None, backtrace: maybe_backtrace(kind), source: Some(Box::new(e)), })) } #[inline] pub fn map ErrorKind>(mut self, f: F) -> Self { self.0.kind = f(self.0.kind); self } #[inline] pub fn kind(&self) -> ErrorKind { self.0.kind } #[inline] pub fn msg(&self) -> Option<&str> { self.0.msg.as_deref() } /// Returns a borrowed value which can display not only this error but also /// the full chain of causes and (where applicable) the stack trace. /// /// The exact format may change. Currently, it displays the stack trace for /// the current error but not any of the sources. #[inline] pub fn chain(&self) -> impl Display + '_ { ErrorChain(self) } } /// Formats this error alone (*not* its full chain). impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0.msg { None => std::fmt::Display::fmt(self.0.kind.grpc_name(), f)?, Some(ref msg) => write!(f, "{}: {}", self.0.kind.grpc_name(), msg)?, } if let Some(ref bt) = self.0.backtrace { // TODO: only with "alternate"/# modifier? // Shorten this, maybe by switching to `backtrace` + using // `backtrace_ext::short_frames_strict` or similar. write!(f, "\nBacktrace:\n{}", bt)?; } Ok(()) } } impl Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&ErrorChain(self), f) } } /// Value returned by [`Error::chain`]. struct ErrorChain<'a>(&'a Error); impl Display for ErrorChain<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(self.0, f)?; let mut source = self.0.source(); while let Some(n) = source { write!(f, "\ncaused by: {}", n)?; source = n.source() } Ok(()) } } impl StdError for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { // https://users.rust-lang.org/t/question-about-error-source-s-static-return-type/34515/8 self.0.source.as_ref().map(|e| e.as_ref() as &_) } } /// Extension methods for `Result`. pub trait ResultExt { /// Annotates an error with the given kind. /// Example: /// ``` /// use moonfire_base::{ErrorKind, ResultExt}; /// use std::io::Read; /// let mut buf = [0u8; 1]; /// let r = std::io::Cursor::new("").read_exact(&mut buf[..]).err_kind(ErrorKind::Internal); /// assert_eq!(r.unwrap_err().kind(), ErrorKind::Internal); /// ``` fn err_kind(self, k: ErrorKind) -> Result; } impl ResultExt for Result where E: StdError + Sync + Send + 'static, { fn err_kind(self, k: ErrorKind) -> Result { self.map_err(|e| ErrorBuilder::default().kind(k).source(e).build()) } } /// Wrapper around `err!` which returns the error. /// /// Example with positional arguments: /// ``` /// use moonfire_base::bail; /// let e = || -> Result<(), moonfire_base::Error> { /// bail!(Unauthenticated, msg("unknown user: {}", "slamb")); /// }().unwrap_err(); /// assert_eq!(e.kind(), moonfire_base::ErrorKind::Unauthenticated); /// assert_eq!(e.to_string(), "UNAUTHENTICATED: unknown user: slamb"); /// ``` /// /// Example with named arguments: /// ``` /// use moonfire_base::bail; /// let e = || -> Result<(), moonfire_base::Error> { /// let user = "slamb"; /// bail!(Unauthenticated, msg("unknown user: {user}")); /// }().unwrap_err(); /// assert_eq!(e.kind(), moonfire_base::ErrorKind::Unauthenticated); /// assert_eq!(e.to_string(), "UNAUTHENTICATED: unknown user: slamb"); /// ``` #[macro_export] macro_rules! bail { ($($arg:tt)+) => { return Err($crate::err!($($arg)+).into()); }; } /// Constructs an [`Error`], tersely. /// /// This is a shorthand way to use [`ErrorBuilder`]. /// /// The first argument is an `Into`, such as the following: /// /// * an [`ErrorKind`] enum variant name like `Unauthenticated`. /// There's an implicit `use ::coded::ErrorKind::*` to allow the bare /// variant names just within this restrictive scope where you're unlikely /// to have conflicts with other identifiers. /// * an [`std::io::Error`] as a source, which sets the new `Error`'s /// `ErrorKind` based on the `std::io::Error`. /// * an `Error` as a source, which similarly copies the `ErrorKind`. /// * an existing `ErrorBuilder`, which does not create a new source link. /// /// Following arguments may be of these forms: /// /// * `msg(...)`, which expands to `.msg(format!(...))`. See [`ErrorBuilder::msg`]. /// * `source(...)`, which simply expands to `.source($src)`. See [`ErrorBuilder::source`]. /// /// ## Examples /// /// Simplest: /// /// ```rust /// # use coded::err; /// let e = err!(InvalidArgument); /// let e = err!(InvalidArgument,); // trailing commas are allowed /// assert_eq!(e.kind(), coded::ErrorKind::InvalidArgument); /// ``` /// /// Constructing with a fixed error variant name: /// /// ```rust /// # use {coded::err, std::error::Error, std::num::ParseIntError}; /// let input = "a12"; /// let src = i32::from_str_radix(input, 10).unwrap_err(); /// /// let e = err!(InvalidArgument, source(src.clone()), msg("bad argument {:?}", input)); /// // The line above is equivalent to: /// let e2 = ::coded::ErrorBuilder::from(::coded::ErrorKind::InvalidArgument) /// .source(src.clone()) /// .msg(format!("bad argument {:?}", input)) /// .build(); /// /// assert_eq!(e.kind(), coded::ErrorKind::InvalidArgument); /// assert_eq!(e.source().unwrap().downcast_ref::().unwrap(), &src); /// ``` /// /// Constructing from an `std::io::Error`: /// /// ```rust /// # use coded::err; /// let e = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); /// let e = err!(e, msg("path {} not found", "foo")); /// assert_eq!(e.kind(), coded::ErrorKind::NotFound); /// ``` #[macro_export] macro_rules! err { // This uses the "incremental TT munchers", "internal rules", and "push-down accumulation" // patterns explained in the excellent "The Little Book of Rust Macros": // . (@accum $body:tt $(,)?) => { $body.build() }; (@accum ($($body:tt)*), source($src:expr) $($tail:tt)*) => { $crate::err!(@accum ($($body)*.source($src)) $($tail)*) }; // msg(...) uses the `format!` form even when there's only the format string. // This can catch errors (e.g. https://github.com/dtolnay/anyhow/issues/55) // and will allow supporting implicit named parameters: // https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html (@accum ($($body:tt)*), msg($format:expr) $($tail:tt)*) => { $crate::err!(@accum ($($body)*.msg(format!($format))) $($tail)*) }; (@accum ($($body:tt)*), msg($format:expr, $($args:tt)*) $($tail:tt)*) => { $crate::err!(@accum ($($body)*.msg(format!($format, $($args)*))) $($tail)*) }; ($builder:expr $(, $($tail:tt)*)? ) => { $crate::err!(@accum ({ use $crate::ErrorKind::*; $crate::ErrorBuilder::from($builder) }) , $($($tail)*)* ) }; }