diff --git a/src/database.rs b/src/database.rs index ab0843a..8412b67 100644 --- a/src/database.rs +++ b/src/database.rs @@ -9,11 +9,24 @@ use std::{ PathBuf, Path, }, + cmp::{ + PartialOrd, + Ordering, + }, + marker::{ + Unpin, + }, }; use chrono::{ DateTime, Utc, }; +use tokio::{ + prelude::*, + io::{ + AsyncRead, + }, +}; #[derive(Debug, PartialEq, Eq)] pub struct Database @@ -22,6 +35,7 @@ pub struct Database database: HashMap, } +/// All versions of a single file #[derive(Debug, PartialEq, Eq)] pub struct Entry { @@ -29,6 +43,7 @@ pub struct Entry versions: HashSet, } +/// A single version of a single file #[derive(Debug, PartialEq, Eq, Hash)] pub struct Version { @@ -37,11 +52,62 @@ pub struct Version size: usize, } +impl PartialOrd for Version +{ + #[inline] fn partial_cmp(&self, other: &Self) -> Option + { + self.date.partial_cmp(&other.date) + } +} + impl Version { /// Gets the lowermost name of the path for this specific version - pub fn get_pathname(&self) -> String + pub fn get_pathname_string(&self) -> String { format!("{}", self.date.timestamp_nanos()) } + + /// Create a new `Version` from a stream source with a specific UTC timestamp. + pub async fn new_specific(timestamp: DateTime, from: &mut R) -> io::Result + where R: AsyncRead + Unpin + { + let (size, hash) = { + let mut hash = hash::Sha256Sum::empty(); + (hash.compute_into(from).await?, hash) + }; + + Ok(Self { + date: timestamp, + hash, + size, + }) + } + + /// Create a new `Version` from a stream sorce with the current UTC timestamp. + pub async fn new(from: &mut R) -> io::Result + where R: AsyncRead + Unpin + { + Self::new_specific(Utc::now(), from).await + } + + /// Length of this `Version` + #[inline] pub fn len(&self) -> usize + { + self.size + } + + /// Timestamp of this `Version` + #[inline] pub fn timestamp(&self) -> &DateTime + { + &self.date + } + + /// Hash of this `Version` + #[inline] pub fn hash(&self) -> &hash::Sha256Sum + { + &self.hash + } + + //TODO: How do we manage serialisation? Do we save all this metadata, or just recalculate it from the filename? } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..979cddf --- /dev/null +++ b/src/error.rs @@ -0,0 +1,260 @@ +//! Error handling stuffs +use std::{ + error, + fmt, +}; + +//TODO: +// this was a complete failure, look at eyre instead or something + +/// Context for an error +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct Context +{ + line: Option, + column: Option, + file: &'static str, +} + +impl Context +{ + pub fn new(line: Option, column: Option, file: &'static str) -> Self + { + Self { + line,column,file + } + } +} + +impl fmt::Display for Context +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{} ", self.file)?; + if let Some(line) = self.line { + write!(f, "{}:", line)?; + } else { + write!(f, "?:")?; + } + if let Some(column) = self.column { + write!(f, "{}", column)?; + } else { + write!(f, "?")?; + } + Ok(()) + } +} + +/// A wrapper over an error with a nicer error message +/// +/// Like doushio's `Muggle`. +pub struct Wrap(E, D); + +pub trait WrappedError +{ + fn long_msg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; + fn short_msg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +impl WrappedError for Wrap +where D: fmt::Display, + E: error::Error + 'static +{ + fn long_msg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.long_message()) + } + fn short_msg(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self) + } +} + +impl Wrap +where D: fmt::Display, + E: error::Error + 'static +{ + /// Get all sources of the error + pub fn all_sources(&self) -> Vec<&(dyn error::Error + 'static)> + { + use std::error::Error; + std::iter::successors(self.source(), |x| x.source()).collect() + } + + /// Into the inner error + pub fn into_inner(self) -> E + { + self.0 + } + + /// Get the full and long error message + pub fn long_message(&self) -> impl fmt::Display + '_ + { + struct LongOutput<'a, E,D>(&'a Wrap); + impl<'a, E,D> fmt::Display for LongOutput<'a, E,D> + where Wrap: error::Error + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + writeln!(f, "{}", self.0)?; + use std::error::Error; + for (i, spec) in (1..).zip(std::iter::successors(self.0.source(), |x| x.source())) + { + writeln!(f, " [{}] -> {}", i, spec)?; + } + + Ok(()) + } + } + + LongOutput(&self) + } +} + +pub trait WrapExt: Sized +{ + /// Wrap this error in another + fn wrap(self, msg: T) -> Wrap + where T: fmt::Display; +} + +impl WrapExt for E where E: error::Error +{ + fn wrap(self, msg: T) -> Wrap + where T: fmt::Display + { + Wrap(self, msg) + } +} + +pub trait WrapErrExt: Sized +{ + /// Wrap the error from this result with a nicer error message + fn wrap_err(self, msg: U) -> Result> + where U: fmt::Display; +} + +impl WrapErrExt for Result +{ + fn wrap_err(self, msg: U) -> Result> + where U: fmt::Display + { + self.map_err(|e| Wrap(e, msg)) + } +} + + +impl fmt::Debug for Wrap +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", std::any::type_name::()) + } +} + +impl fmt::Display for Wrap +where D: fmt::Display +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.1) + } +} + +impl error::Error for Wrap +where E: error::Error + 'static, + D: fmt::Display +{ + fn source(&self) -> Option<&(dyn error::Error + 'static)> + { + Some(&self.0) + } +} + +/// An error with a context +#[derive(Debug)] +pub struct ContextError(pub E, pub Context) +where E: fmt::Debug; + +impl WrappedError for ContextError +where E: fmt::Debug + fmt::Display +{ + fn long_msg(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self) + } + fn short_msg(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.0) + } +} + +impl fmt::Display for ContextError +where E: fmt::Display + fmt::Debug +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}: {}", self.1, self.0) + } +} + +impl error::Error for ContextError +where E: fmt::Display + fmt::Debug{} + +#[macro_export] + macro_rules! context { + () => ($crate::error::Context::new(Some(line!()), Some(column!()), file!())); +} + +/// Construct a new error with context +#[macro_export] macro_rules! error { + ($err:expr) => ($crate::error::ContextError($err, $crate::context!())); + ($fmt:literal $($tt:tt)*) => ($crate::error!(format!($fmt $($tt)*))); +} + +pub struct ErrorStack(Box); + +impl From for ErrorStack +{ + fn from(from: T) -> Self + { + Self(Box::new(from)) + } +} + +impl fmt::Display for ErrorStack +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + self.0.long_msg(f) + } +} + +impl fmt::Debug for ErrorStack +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f,"") + } +} + +impl error::Error for ErrorStack{} + +impl ErrorStack +{ + /// Get the short message from this stack + pub fn short_message(&self) -> impl fmt::Display + '_ + { + pub struct ShortMessage<'a>(&'a ErrorStack); + + impl<'a> fmt::Display for ShortMessage<'a> + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + self.0.0.short_msg(f) + } + } + + ShortMessage(self) + } +} diff --git a/src/main.rs b/src/main.rs index daf5366..64def7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,11 @@ use cfg_if::cfg_if; mod ext; use ext::*; +mod error; +use error::{ + WrapErrExt as _, + WrapExt as _, +}; mod consts; mod util; mod hash; @@ -13,9 +18,21 @@ mod metadata; mod resolve; mod database; -async fn begin() -> Result> +fn inner() -> Result<(), error::ErrorStack> { + Err(error!("constructing an error"))?; + Ok(()) +} +fn outer() -> Result<(), error::ErrorStack> +{ + inner().wrap_err("propagating an error")?; + Ok(()) +} + +async fn begin() -> Result +{ + outer().wrap_err("outer failed").wrap_err("outer failed twice!")?; Ok(0) } @@ -24,7 +41,8 @@ async fn main() { std::process::exit(match begin().await { Ok(0) => return, Err(err) => { - eprintln!("\nexited with error: {}", err); + eprintln!("\nexited with error: {}", err.short_message()); + println!("\n{}", err); 1 }, Ok(v) => v