use std::fmt::{Display, Formatter};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum ErrorType {
Io,
End,
Str,
Tag,
VlqTooBig,
VlqDecode,
ReadExpect,
FileOpen,
FileCreate,
InvalidFile,
RunningStatus,
StringTooLong,
TooManyTracks,
TrackTooLong,
Other,
}
impl ErrorType {
fn description(self) -> &'static str {
match self {
ErrorType::Io => "an I/O error occurred",
ErrorType::End => "the data ended unexpectedly",
ErrorType::Str => "expected a string but found non-UTF-8 bytes",
ErrorType::Tag => "the chunk tag was not the expected one",
ErrorType::VlqTooBig => "too many bytes while reading a variable-length quantity",
ErrorType::VlqDecode => "unable to decode a variable-length quantity",
ErrorType::ReadExpect => "a byte did not have the required value",
ErrorType::FileOpen => "unable to open the file",
ErrorType::FileCreate => "unable to create the file",
ErrorType::InvalidFile => "The MIDI file is invalid",
ErrorType::RunningStatus => "expected a running status byte but found none",
ErrorType::StringTooLong => "the string is too long and overflows a u32",
ErrorType::TooManyTracks => "there are too many tracks for a 16-bit uint",
ErrorType::TrackTooLong => "the track is too long and overflows a u32",
ErrorType::Other => "unknown error",
}
}
}
impl Display for ErrorType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.description())
}
}
#[derive(Debug)]
pub struct Error {
error_type: ErrorType,
site: String,
message: Option<String>,
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
impl Error {
pub fn new<S: Into<String>>(error_type: ErrorType, site: S) -> Self {
Self {
error_type,
site: site.into(),
message: None,
source: None,
}
}
pub fn with_message<S: Into<String>>(mut self, message: S) -> Self {
self.message = Some(message.into());
self
}
pub fn with_source<E>(mut self, source: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
self.source = Some(Box::new(source));
self
}
pub fn error_type(&self) -> ErrorType {
self.error_type
}
pub fn site(&self) -> &str {
&self.site
}
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.site, self.error_type)?;
if let Some(message) = &self.message {
write!(f, ": {}", message)?;
}
if let Some(source) = &self.source {
write!(f, ": {}", source)?;
}
Ok(())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|e| e.as_ref() as _)
}
}
pub struct ErrorContext {
error_type: ErrorType,
site: String,
message: Option<String>,
}
impl ErrorContext {
pub fn new<S: Into<String>>(error_type: ErrorType, site: S) -> Self {
Self {
error_type,
site: site.into(),
message: None,
}
}
pub fn with_message<S: Into<String>>(mut self, message: S) -> Self {
self.message = Some(message.into());
self
}
pub fn into_error(self) -> Error {
let error = Error::new(self.error_type, self.site);
match self.message {
Some(message) => error.with_message(message),
None => error,
}
}
pub fn fail<T>(self) -> Result<T> {
Err(self.into_error())
}
}
pub trait Context<T> {
fn context<F>(self, context: F) -> Result<T>
where
F: FnOnce() -> ErrorContext;
}
impl<T, E> Context<T> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn context<F>(self, context: F) -> Result<T>
where
F: FnOnce() -> ErrorContext,
{
self.map_err(|e| context().into_error().with_source(e))
}
}
impl<T> Context<T> for Option<T> {
fn context<F>(self, context: F) -> Result<T>
where
F: FnOnce() -> ErrorContext,
{
self.ok_or_else(|| context().into_error())
}
}
macro_rules! site {
() => {
format!("{}:{}", file!(), line!())
};
}
macro_rules! ctx {
($error_type:expr) => {
|| crate::error::ErrorContext::new($error_type, site!())
};
($error_type:expr, $msg:expr) => {
|| crate::error::ErrorContext::new($error_type, site!()).with_message($msg)
};
($error_type:expr, $fmt:expr, $($arg:expr),+) => {
|| crate::error::ErrorContext::new($error_type, site!())
.with_message(format!($fmt, $($arg),+))
};
}
macro_rules! io {
() => {
ctx!(crate::error::ErrorType::Io)
};
}
macro_rules! wr {
() => {
ctx!(crate::error::ErrorType::Io)
};
}
macro_rules! ensure {
($condition:expr, $context:expr) => {
if !$condition {
return $context().fail();
}
};
}
macro_rules! invalid_file_e {
() => {
ctx!(crate::error::ErrorType::InvalidFile)().into_error()
};
($msg:expr) => {
ctx!(crate::error::ErrorType::InvalidFile, $msg)().into_error()
};
($fmt:expr, $($arg:expr),+) => {
ctx!(crate::error::ErrorType::InvalidFile, $fmt, $($arg),+)().into_error()
};
}
macro_rules! invalid_file {
() => {
return Err(invalid_file_e!())
};
($msg:expr) => {
return Err(invalid_file_e!($msg))
};
($fmt:expr, $($arg:expr),+) => {
return Err(invalid_file_e!($fmt, $($arg),+))
};
}
#[test]
fn site_test() {
let line = line!() + 1;
let site = site!();
assert!(site.contains("error.rs"));
assert!(site.contains(format!("{}", line).as_str()));
}
#[test]
fn invalid_file_macros_test_no_message() {
fn foo() -> Result<u64> {
invalid_file!();
}
let result = foo();
assert!(result.is_err());
let message = format!("{}", result.err().unwrap());
assert!(message.as_str().contains("The MIDI file is invalid"));
}
#[test]
fn invalid_file_macros_test_message() {
fn foo() -> Result<u64> {
let flerbin = String::from("flerbin");
invalid_file!(flerbin);
}
let result = foo();
assert!(result.is_err());
let message = format!("{}", result.err().unwrap());
assert!(message.as_str().contains("flerbin"));
}
#[test]
fn invalid_file_macros_test_fmt() {
fn foo() -> Result<u64> {
invalid_file!("hello {}, {}", "world", String::from("foo"));
}
let result = foo();
assert!(result.is_err());
let message = format!("{}", result.err().unwrap());
assert!(message.as_str().contains("hello world, foo"));
}