From 35ad19a14278a7d9102a21e4a6921a3ea59d1f61 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Tue, 24 Nov 2015 01:20:48 +0100 Subject: [PATCH 1/8] error handling - first draft --- src/error/api.rs | 25 +++++ src/error/macros.rs | 83 +++++++++++++++ src/error/mod.rs | 187 ++++++++++++++++++++++++++++++++++ src/lib.rs | 24 +++++ src/test_error/error_codes.rs | 77 ++++++++++++++ src/test_error/mod.rs | 63 ++++++++++++ 6 files changed, 459 insertions(+) create mode 100644 src/error/api.rs create mode 100644 src/error/macros.rs create mode 100644 src/error/mod.rs create mode 100644 src/test_error/error_codes.rs create mode 100644 src/test_error/mod.rs diff --git a/src/error/api.rs b/src/error/api.rs new file mode 100644 index 0000000..df21535 --- /dev/null +++ b/src/error/api.rs @@ -0,0 +1,25 @@ +// This file is part of Twig (ported to Rust). +// +// For the copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Rust macro +/// +/// @author Colin Kiegel + +///////////// +// imports // +///////////// + + + +///////////// +// exports // +///////////// + + +// will be used to transform error codes! +// can't use Into-trait - because we only have references +pub trait GeneralizeTo { + fn generalize(&self) -> T; +} diff --git a/src/error/macros.rs b/src/error/macros.rs new file mode 100644 index 0000000..c9cd117 --- /dev/null +++ b/src/error/macros.rs @@ -0,0 +1,83 @@ +// This file is part of Twig (ported to Rust). +// +// For the copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Rust macro +/// +/// @author Colin Kiegel + + +///////////// +// exports // +///////////// + +pub use super::*; + +#[macro_export] +macro_rules! err { + ( $code:expr ) => ({ + ::error::Exception::new($code, err_location!()) + }); +} + +#[macro_export] +macro_rules! err_location { + () => ({ + ::error::Location { + module_path : module_path!(), + filename : file!(), + line : line!(), + column : column!(), + } + }); +} + +// NOTE: because convert::From already is reflexive (generic `impl From for T`) +// we can't generically `impl From> for Exception where ...` +// - too bad! +// TODO: file a bug! + +#[macro_export] +macro_rules! impl_convert_exception { + ( $source_type:ty, $target_type:ty, $target_error_code:expr ) => ( + impl ::std::convert::From<::error::Exception<$source_type>> for ::error::Exception<$target_type> { + fn from(cause: ::error::Exception<$source_type>) -> ::error::Exception<$target_type> { + + // NOTE can't use `err_location!(None)` here + // because that would yield linenumbers from + // calls to impl_convert_exception and not from + // the implicit call-site of .into() ... + // + // alternative: manual conversion via different macro + // try_into!() or sth. like that? + + ::error::Exception::new($target_error_code, cause.location()) + .caused_by(cause) + } + } + ); +} + + +// `try_chain!`-macro will create an error-chain with location for each chaining-operation +// +// use as follows +// +// fn foo() -> Result<(), Exception> { +// let result_B: Result<(), Exception> = ...; +// +// try_chain!(result_B); +// } +macro_rules! try_chain { + ( $result:expr ) => ( + match($result) { + Ok(value) => value, + Err(cause) => { + return err!(::error::api::GeneralizeTo::generalize(cause.code())) + .caused_by(cause) + .into() + } + } + ) +} diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..4803ee9 --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,187 @@ +// This file is part of Twig (ported to Rust). +// +// For the copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Twig base exception. +/// +/// @author Colin Kiegel + + +///////////// +// imports // +///////////// + +use std::fmt::{self, Debug, Display}; +use std::any::Any; +use std::convert::Into; + +///////////// +// exports // +///////////// + +#[macro_use] +pub mod macros; +// use std Error-trait to improve cross-crate compatibility +// don't mix it up with Err(X) +pub use std::error::Error; +pub mod api; + +// however this means, we have to call our objects differently ... I suggest Exception +pub struct Exception + where T: Any + Debug + Display +{ + // the exception codes are going to be enums + // - i.e. Exception implements std::error::Error without any boilerplate + // to MY_ENUM. Hurray! :-) + code: T, + // I decided to call this field `code` instead of `error` to not confuse it with the Error trait + location: Location, + // chaining is required by std::error::Error + cause: Option>, + // description buffer is required by std::error::Error - it is a compilation of code + location + // + // It's hard to compile this string lazily - even with Option>, due to the API + // of std::error::Error. So we have to precompile this string for now, even if someone + // just throws away the Exception. :-/ + description: String +} + +impl Exception + where T: Any + Debug + Display +{ + pub fn new(code: T, location: Location) -> Exception { + let description = format!("{error_code} at {location}", + error_code = code, + location = location.to_string()); + + Exception { + code: code, + location: location, + cause: None, + description: description + } + } + + #[allow(dead_code)] // only used by tests + pub fn code(&self) -> &T { + &self.code + } + + pub fn location(&self) -> &Location { + &self.location + } + + // I would opt to remove(!) this method in favour of more complex enum error-codes + // with additional data like you are using right now. Removal would force us to put + // all useful information into the enums. But that might turn out to be useful afterwards. + // We could gain much information by just Debug-formatting our errors / exceptions. + // Good-Bye pre-parsed strings. ;-) + pub fn explain(mut self, _message: String) -> Self { + unimplemented!() + } + + pub fn caused_by(mut self, cause: X) -> Self { + self.cause = Some(Box::new(cause)); + + self + } + + pub fn causes(self, wrapper: Exception) -> Exception where + X: Any + Debug + Display + { + wrapper.caused_by(self) + } + + // iterate along the error-chain. + pub fn iter(&self) -> ErrorIter { + ErrorIter { + next: Some(self), + } + } +} + +impl Error for Exception + where T: Any + Debug + Display +{ + fn description(&self) -> &str { + &self.description + } + + fn cause<'a>(&'a self) -> Option<&'a Error> { + self.cause.as_ref().map(|x| &**x) // dereference from Option> to Option<&T> + } +} + +// Exception -> Err(Exception) +impl Into>> for Exception + where T: Any + Debug + Display +{ + fn into (self) -> Result> { + Err(self) + } +} + + + +pub struct ErrorIter<'a> { + next: Option<&'a Error> +} + +impl<'a> Iterator for ErrorIter<'a> { + type Item = &'a Error; + + fn next(&mut self) -> Option { + return match self.next { + Some(err) => { + self.next = err.cause(); + Some(err) + } + None => None, + } + } +} + +impl Display for Exception + where T: Any + Debug + Display +{ + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + let recursive_desc = self.iter().map(|e| e.description()) + .collect::>().join(" caused by\n - "); + write!(f, "\n - {}\n", recursive_desc) + } +} + +impl Debug for Exception + where T: Any + Debug + Display +{ + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + // custom implementation - skipping description, which would be somewhat redundant + f.debug_struct("Exception") + .field("location", &self.location) + .field("code", &self.code) + .field("cause", &self.cause) + .finish() + } +} + +#[derive(Debug)] +pub struct Location { + // this might be a bit redundant - but we just store everything we can get. + // we don't need to be super performant on exceptions - because we try to avoid them :-) + // + // note that the module_path is currently only displayed in Debug output due to this redundancy + pub module_path : &'static str, // e.g. twig::lexer::job::state::shared_traits + pub filename : &'static str, // e.g. /src/lexer/job/state/shared_traits.rs + pub line : u32, + pub column : u32, +} + +impl ToString for Location { + fn to_string(&self) -> String { + format!("{filename}:{line}:{column}", + filename = self.filename, + line = self.line, + column = self.column) + } +} diff --git a/src/lib.rs b/src/lib.rs index a93251b..052987e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,27 @@ +// This file is part of Twig (ported to Rust). +// +// For the copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Twig library for rust +/// +/// @author Colin Kiegel + +///////////// +// imports // +///////////// + + + +///////////// +// exports // +///////////// + +#[macro_use] +pub mod error; + +mod test_error; + #[test] fn it_works() { } diff --git a/src/test_error/error_codes.rs b/src/test_error/error_codes.rs new file mode 100644 index 0000000..f923b8b --- /dev/null +++ b/src/test_error/error_codes.rs @@ -0,0 +1,77 @@ +// This file is part of rust-web/twig +// +// For the copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Twig library for rust +/// +/// @author NAME + +///////////// +// imports // +///////////// + +#[macro_use] +use error::Exception; +use error::api::GeneralizeTo; +use std::fmt::{self, Display}; + +///////////// +// exports // +///////////// + +pub type EngineError = Exception; +pub type EngineResult = Result; + +pub type RuntimeError = Exception; +pub type RuntimeResult = Result; + +impl GeneralizeTo for RuntimeErrorCode { + fn generalize(&self) -> EngineErrorCode { + EngineErrorCode::RuntimeError + } +} + +#[derive(Clone, Debug)] +pub enum EngineErrorCode { + TemplateNotFound { name: String, search_paths: Vec }, + RuntimeError +} + +impl Display for EngineErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + EngineErrorCode::TemplateNotFound { ref name, ref search_paths } => { + if search_paths.len() == 0 { + // note: use {:?} instead of \"{}\" for strings + // -> easier to write + // -> escapes quotes within strings + write!(f, "Template {:?} was not found", name) + } else { + try!(write!(f, "Template {:?} was not found, looked in ", name)); + write!(f, "{:?}", search_paths) + } + }, + EngineErrorCode::RuntimeError => { + write!(f, "Some Runtime Error") + } + } + } +} + +#[derive(Clone, Debug)] +/// Runtime error message. +pub enum RuntimeErrorCode { + /// Callable invoked with argument count that does not match defined count. + InvalidArgumentCount { defined: usize, given: usize }, +} + +impl fmt::Display for RuntimeErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + RuntimeErrorCode::InvalidArgumentCount { ref defined, ref given } => { + write!(f, "Target requires {} arguments, called with {}", defined, given) + } + } + } +} diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs new file mode 100644 index 0000000..5fad4a6 --- /dev/null +++ b/src/test_error/mod.rs @@ -0,0 +1,63 @@ +// This file is part of rust-web/twig +// +// For the copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/// Twig library for rust +/// +/// @author NAME + +///////////// +// imports // +///////////// + +mod error_codes; +use self::error_codes::{EngineResult, EngineErrorCode, RuntimeResult, RuntimeErrorCode}; + +///////////// +// exports // +///////////// + + +fn foo_engine_error() -> EngineResult<()> { + err!(EngineErrorCode::TemplateNotFound { + name: "hello_world.html.twig".to_string(), + search_paths: vec![] + }).into() +} + +fn foo_runtime_error() -> RuntimeResult<()> { + err!(RuntimeErrorCode::InvalidArgumentCount{ + defined: 10, + given: 4 + }).into() +} + +fn foo_chain_error() -> EngineResult<()> { + let val: () = try_chain!(foo_runtime_error()); + + unreachable!() +} + +#[test] +fn test() { + println!("engine error {}", foo_engine_error().unwrap_err()); + println!("runtime error {}", foo_runtime_error().unwrap_err()); + + println!("chained error {}", foo_chain_error().unwrap_err()); + + assert!(false); + + // ---- test_error::test stdout ---- + // engine error + // - Template "hello_world.html.twig" was not found at src/test_error.rs:80:4 + // + // runtime error + // - Target requires 10 arguments, called with 4 at src/test_error.rs:87:4 + // + // chained error + // - Some Runtime Error at src/test_error.rs:94:18 caused by + // - Target requires 10 arguments, called with 4 at src/test_error.rs:87:4 + // + // thread 'test_error::test' panicked at 'assertion failed: false', src/test_error.rs:108 +} From 0e82528f7671ae6833ae27fa9b98edc2b7c17d63 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Tue, 24 Nov 2015 20:30:31 +0100 Subject: [PATCH 2/8] compact rust-headers --- src/error/api.rs | 15 +------------- src/error/macros.rs | 37 ++--------------------------------- src/error/mod.rs | 12 +----------- src/lib.rs | 13 ------------ src/test_error/error_codes.rs | 9 --------- src/test_error/mod.rs | 10 ---------- 6 files changed, 4 insertions(+), 92 deletions(-) diff --git a/src/error/api.rs b/src/error/api.rs index df21535..e379864 100644 --- a/src/error/api.rs +++ b/src/error/api.rs @@ -3,20 +3,7 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Rust macro -/// -/// @author Colin Kiegel - -///////////// -// imports // -///////////// - - - -///////////// -// exports // -///////////// - +/// Twig Error API for error code conversion // will be used to transform error codes! // can't use Into-trait - because we only have references diff --git a/src/error/macros.rs b/src/error/macros.rs index c9cd117..ac933e2 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -3,17 +3,11 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Rust macro -/// -/// @author Colin Kiegel - - -///////////// -// exports // -///////////// +/// Twig macro for error handling pub use super::*; + #[macro_export] macro_rules! err { ( $code:expr ) => ({ @@ -33,33 +27,6 @@ macro_rules! err_location { }); } -// NOTE: because convert::From already is reflexive (generic `impl From for T`) -// we can't generically `impl From> for Exception where ...` -// - too bad! -// TODO: file a bug! - -#[macro_export] -macro_rules! impl_convert_exception { - ( $source_type:ty, $target_type:ty, $target_error_code:expr ) => ( - impl ::std::convert::From<::error::Exception<$source_type>> for ::error::Exception<$target_type> { - fn from(cause: ::error::Exception<$source_type>) -> ::error::Exception<$target_type> { - - // NOTE can't use `err_location!(None)` here - // because that would yield linenumbers from - // calls to impl_convert_exception and not from - // the implicit call-site of .into() ... - // - // alternative: manual conversion via different macro - // try_into!() or sth. like that? - - ::error::Exception::new($target_error_code, cause.location()) - .caused_by(cause) - } - } - ); -} - - // `try_chain!`-macro will create an error-chain with location for each chaining-operation // // use as follows diff --git a/src/error/mod.rs b/src/error/mod.rs index 4803ee9..3185089 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -3,22 +3,12 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Twig base exception. -/// -/// @author Colin Kiegel - - -///////////// -// imports // -///////////// +/// Twig base exception use std::fmt::{self, Debug, Display}; use std::any::Any; use std::convert::Into; -///////////// -// exports // -///////////// #[macro_use] pub mod macros; diff --git a/src/lib.rs b/src/lib.rs index 052987e..050500f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,22 +4,9 @@ // file that was distributed with this source code. /// Twig library for rust -/// -/// @author Colin Kiegel - -///////////// -// imports // -///////////// - - - -///////////// -// exports // -///////////// #[macro_use] pub mod error; - mod test_error; #[test] diff --git a/src/test_error/error_codes.rs b/src/test_error/error_codes.rs index f923b8b..307309c 100644 --- a/src/test_error/error_codes.rs +++ b/src/test_error/error_codes.rs @@ -4,21 +4,12 @@ // file that was distributed with this source code. /// Twig library for rust -/// -/// @author NAME - -///////////// -// imports // -///////////// #[macro_use] use error::Exception; use error::api::GeneralizeTo; use std::fmt::{self, Display}; -///////////// -// exports // -///////////// pub type EngineError = Exception; pub type EngineResult = Result; diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs index 5fad4a6..6e36186 100644 --- a/src/test_error/mod.rs +++ b/src/test_error/mod.rs @@ -4,20 +4,10 @@ // file that was distributed with this source code. /// Twig library for rust -/// -/// @author NAME - -///////////// -// imports // -///////////// mod error_codes; use self::error_codes::{EngineResult, EngineErrorCode, RuntimeResult, RuntimeErrorCode}; -///////////// -// exports // -///////////// - fn foo_engine_error() -> EngineResult<()> { err!(EngineErrorCode::TemplateNotFound { From 6dd293341126eccb29e392c41da8305cf32514a1 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Fri, 27 Nov 2015 15:08:05 +0100 Subject: [PATCH 3/8] minor refactoring for error handling --- src/error/api.rs | 26 +++++++- src/error/macros.rs | 6 +- src/error/mod.rs | 108 +++++++++++++--------------------- src/lib.rs | 2 + src/test_error/error_codes.rs | 43 +++++++++----- src/test_error/mod.rs | 43 +++++++------- 6 files changed, 119 insertions(+), 109 deletions(-) diff --git a/src/error/api.rs b/src/error/api.rs index e379864..2f75d25 100644 --- a/src/error/api.rs +++ b/src/error/api.rs @@ -3,7 +3,31 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Twig Error API for error code conversion +/// Twig Error API for error codes + +use error::{Error, Location}; +use std::fmt::{Debug, Display}; +use std::any::Any; + +/// Base functionality for error codes +/// - similar to std::error::Error, but without error-chaining! +/// +/// +pub trait ErrorCode: Debug + Display + Any { + /// A short description of the error code. + /// + /// The description should not contain newlines or sentence-ending + /// punctuation, to facilitate embedding in larger user-facing + /// strings. + fn description(&self) -> &str; + + /// Provide the location, where the error occured. + fn at(self, location: Location) -> Error where + Self: Sized + { + Error::new(self, location) + } +} // will be used to transform error codes! // can't use Into-trait - because we only have references diff --git a/src/error/macros.rs b/src/error/macros.rs index ac933e2..ba3615d 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -11,12 +11,12 @@ pub use super::*; #[macro_export] macro_rules! err { ( $code:expr ) => ({ - ::error::Exception::new($code, err_location!()) + ::error::Error::new($code, location!()) }); } #[macro_export] -macro_rules! err_location { +macro_rules! location { () => ({ ::error::Location { module_path : module_path!(), @@ -38,7 +38,7 @@ macro_rules! err_location { // } macro_rules! try_chain { ( $result:expr ) => ( - match($result) { + match $result { Ok(value) => value, Err(cause) => { return err!(::error::api::GeneralizeTo::generalize(cause.code())) diff --git a/src/error/mod.rs b/src/error/mod.rs index 3185089..c94b26d 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -5,8 +5,7 @@ /// Twig base exception -use std::fmt::{self, Debug, Display}; -use std::any::Any; +use std::fmt::{self, Display}; use std::convert::Into; @@ -14,12 +13,13 @@ use std::convert::Into; pub mod macros; // use std Error-trait to improve cross-crate compatibility // don't mix it up with Err(X) -pub use std::error::Error; +pub use std::error::Error as StdError; pub mod api; -// however this means, we have to call our objects differently ... I suggest Exception -pub struct Exception - where T: Any + Debug + Display +// generic wrapper around some StdError - adds location support +#[derive(Debug)] +pub struct Error + where T: api::ErrorCode { // the exception codes are going to be enums // - i.e. Exception implements std::error::Error without any boilerplate @@ -28,28 +28,17 @@ pub struct Exception // I decided to call this field `code` instead of `error` to not confuse it with the Error trait location: Location, // chaining is required by std::error::Error - cause: Option>, - // description buffer is required by std::error::Error - it is a compilation of code + location - // - // It's hard to compile this string lazily - even with Option>, due to the API - // of std::error::Error. So we have to precompile this string for now, even if someone - // just throws away the Exception. :-/ - description: String + cause: Option>, } -impl Exception - where T: Any + Debug + Display +impl Error + where T: api::ErrorCode { - pub fn new(code: T, location: Location) -> Exception { - let description = format!("{error_code} at {location}", - error_code = code, - location = location.to_string()); - - Exception { + pub fn new(code: T, location: Location) -> Error { + Error { code: code, location: location, - cause: None, - description: description + cause: None } } @@ -62,23 +51,14 @@ impl Exception &self.location } - // I would opt to remove(!) this method in favour of more complex enum error-codes - // with additional data like you are using right now. Removal would force us to put - // all useful information into the enums. But that might turn out to be useful afterwards. - // We could gain much information by just Debug-formatting our errors / exceptions. - // Good-Bye pre-parsed strings. ;-) - pub fn explain(mut self, _message: String) -> Self { - unimplemented!() - } - - pub fn caused_by(mut self, cause: X) -> Self { + pub fn caused_by(mut self, cause: X) -> Self { self.cause = Some(Box::new(cause)); self } - pub fn causes(self, wrapper: Exception) -> Exception where - X: Any + Debug + Display + pub fn causes(self, wrapper: Error) -> Error where + X: api::ErrorCode { wrapper.caused_by(self) } @@ -91,35 +71,35 @@ impl Exception } } -impl Error for Exception - where T: Any + Debug + Display +impl StdError for Error + where T: api::ErrorCode { fn description(&self) -> &str { - &self.description + // delegate the error description to the ErrorCode + &self.code.description() } - fn cause<'a>(&'a self) -> Option<&'a Error> { - self.cause.as_ref().map(|x| &**x) // dereference from Option> to Option<&T> + fn cause<'a>(&'a self) -> Option<&'a StdError> { + // dereference from Option> to Option<&T> + self.cause.as_ref().map(|x| &**x) } } -// Exception -> Err(Exception) -impl Into>> for Exception - where T: Any + Debug + Display +// Error -> Err(Error) +impl Into>> for Error + where T: api::ErrorCode { - fn into (self) -> Result> { + fn into (self) -> Result> { Err(self) } } - - pub struct ErrorIter<'a> { - next: Option<&'a Error> + next: Option<&'a StdError> } impl<'a> Iterator for ErrorIter<'a> { - type Item = &'a Error; + type Item = &'a StdError; fn next(&mut self) -> Option { return match self.next { @@ -132,26 +112,18 @@ impl<'a> Iterator for ErrorIter<'a> { } } -impl Display for Exception - where T: Any + Debug + Display +impl Display for Error + where T: api::ErrorCode { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let recursive_desc = self.iter().map(|e| e.description()) - .collect::>().join(" caused by\n - "); - write!(f, "\n - {}\n", recursive_desc) - } -} + try!(write!(f, "{error_code} at {location}\n", + error_code = self.code, + location = self.location)); -impl Debug for Exception - where T: Any + Debug + Display -{ - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - // custom implementation - skipping description, which would be somewhat redundant - f.debug_struct("Exception") - .field("location", &self.location) - .field("code", &self.code) - .field("cause", &self.cause) - .finish() + match self.cause { + None => Ok(()), + Some(ref cause) => write!(f, " - caused by: {}", cause), + } } } @@ -167,9 +139,9 @@ pub struct Location { pub column : u32, } -impl ToString for Location { - fn to_string(&self) -> String { - format!("{filename}:{line}:{column}", +impl Display for Location { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{filename}:{line}:{column}", filename = self.filename, line = self.line, column = self.column) diff --git a/src/lib.rs b/src/lib.rs index 050500f..cb72513 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ #[macro_use] pub mod error; + +#[cfg(test)] mod test_error; #[test] diff --git a/src/test_error/error_codes.rs b/src/test_error/error_codes.rs index 307309c..be94437 100644 --- a/src/test_error/error_codes.rs +++ b/src/test_error/error_codes.rs @@ -5,16 +5,15 @@ /// Twig library for rust -#[macro_use] -use error::Exception; -use error::api::GeneralizeTo; +use error::api::{ErrorCode, GeneralizeTo}; +use error::Error; use std::fmt::{self, Display}; -pub type EngineError = Exception; +pub type EngineError = Error; pub type EngineResult = Result; -pub type RuntimeError = Exception; +pub type RuntimeError = Error; pub type RuntimeResult = Result; impl GeneralizeTo for RuntimeErrorCode { @@ -29,22 +28,26 @@ pub enum EngineErrorCode { RuntimeError } +impl ErrorCode for EngineErrorCode { + fn description(&self) -> &str { + match *self { + EngineErrorCode::TemplateNotFound{..} => "Template not found", + EngineErrorCode::RuntimeError => "Runtime error", + } + } +} + impl Display for EngineErrorCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { EngineErrorCode::TemplateNotFound { ref name, ref search_paths } => { - if search_paths.len() == 0 { - // note: use {:?} instead of \"{}\" for strings - // -> easier to write - // -> escapes quotes within strings - write!(f, "Template {:?} was not found", name) - } else { - try!(write!(f, "Template {:?} was not found, looked in ", name)); - write!(f, "{:?}", search_paths) - } + // note: use {:?} instead of \"{}\" for strings + // -> easier to write + // -> escapes quotes within strings + write!(f, "Template {:?} not found, looked in {:?}", name, search_paths) }, EngineErrorCode::RuntimeError => { - write!(f, "Some Runtime Error") + write!(f, "Some runtime error") } } } @@ -57,7 +60,15 @@ pub enum RuntimeErrorCode { InvalidArgumentCount { defined: usize, given: usize }, } -impl fmt::Display for RuntimeErrorCode { +impl ErrorCode for RuntimeErrorCode { + fn description(&self) -> &str { + match *self { + RuntimeErrorCode::InvalidArgumentCount{..} => "Invalid Argument Count", + } + } +} + +impl Display for RuntimeErrorCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { RuntimeErrorCode::InvalidArgumentCount { ref defined, ref given } => { diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs index 6e36186..5bea23e 100644 --- a/src/test_error/mod.rs +++ b/src/test_error/mod.rs @@ -7,7 +7,7 @@ mod error_codes; use self::error_codes::{EngineResult, EngineErrorCode, RuntimeResult, RuntimeErrorCode}; - +use error::api::ErrorCode; fn foo_engine_error() -> EngineResult<()> { err!(EngineErrorCode::TemplateNotFound { @@ -24,30 +24,31 @@ fn foo_runtime_error() -> RuntimeResult<()> { } fn foo_chain_error() -> EngineResult<()> { - let val: () = try_chain!(foo_runtime_error()); + let _val: () = try_chain!(foo_runtime_error()); unreachable!() } #[test] fn test() { - println!("engine error {}", foo_engine_error().unwrap_err()); - println!("runtime error {}", foo_runtime_error().unwrap_err()); - - println!("chained error {}", foo_chain_error().unwrap_err()); - - assert!(false); - - // ---- test_error::test stdout ---- - // engine error - // - Template "hello_world.html.twig" was not found at src/test_error.rs:80:4 - // - // runtime error - // - Target requires 10 arguments, called with 4 at src/test_error.rs:87:4 - // - // chained error - // - Some Runtime Error at src/test_error.rs:94:18 caused by - // - Target requires 10 arguments, called with 4 at src/test_error.rs:87:4 - // - // thread 'test_error::test' panicked at 'assertion failed: false', src/test_error.rs:108 + // these testcases are of course highly unstable due to the error locations(!) + // -> just for demonstration right now: + + let my_error = EngineErrorCode::RuntimeError + .at(location!()) + .caused_by(foo_runtime_error().unwrap_err()); + + assert_eq!(my_error.to_string(), + "Some runtime error at src/test_error/mod.rs:38:12\n \ + - caused by: Target requires 10 arguments, called with 4 at src/test_error/mod.rs:20:4\n"); + + assert_eq!(foo_engine_error().unwrap_err().to_string(), + "Template \"hello_world.html.twig\" not found, looked in [] at src/test_error/mod.rs:13:4\n"); + + assert_eq!(foo_runtime_error().unwrap_err().to_string(), + "Target requires 10 arguments, called with 4 at src/test_error/mod.rs:20:4\n"); + + assert_eq!(foo_chain_error().unwrap_err().to_string(), + "Some runtime error at src/test_error/mod.rs:27:19\n \ + - caused by: Target requires 10 arguments, called with 4 at src/test_error/mod.rs:20:4\n"); } From 599472d7037e5ff71951d8e3090e44de58ff0a09 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Fri, 27 Nov 2015 15:17:51 +0100 Subject: [PATCH 4/8] minor refactoring for error handling II --- src/error/macros.rs | 8 +++++--- src/test_error/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/error/macros.rs b/src/error/macros.rs index ba3615d..0bb26c3 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -11,7 +11,7 @@ pub use super::*; #[macro_export] macro_rules! err { ( $code:expr ) => ({ - ::error::Error::new($code, location!()) + Err(::error::Error::new($code, location!())) }); } @@ -41,9 +41,11 @@ macro_rules! try_chain { match $result { Ok(value) => value, Err(cause) => { - return err!(::error::api::GeneralizeTo::generalize(cause.code())) + let code = ::error::api::GeneralizeTo::generalize(cause.code()); + + return Err(::error::Error::new(code, location!()) .caused_by(cause) - .into() + .into()) } } ) diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs index 5bea23e..550e0a2 100644 --- a/src/test_error/mod.rs +++ b/src/test_error/mod.rs @@ -13,14 +13,14 @@ fn foo_engine_error() -> EngineResult<()> { err!(EngineErrorCode::TemplateNotFound { name: "hello_world.html.twig".to_string(), search_paths: vec![] - }).into() + }) } fn foo_runtime_error() -> RuntimeResult<()> { err!(RuntimeErrorCode::InvalidArgumentCount{ defined: 10, given: 4 - }).into() + }) } fn foo_chain_error() -> EngineResult<()> { From 4f86bc8b7c557cca50ac0fb1052f92604fa6e6a4 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Fri, 27 Nov 2015 22:41:17 +0100 Subject: [PATCH 5/8] minor refactoring for error handling III --- src/error/mod.rs | 28 +++++++++------------------- src/test_error/error_codes.rs | 29 ++++++++++++++++++++++++----- src/test_error/mod.rs | 15 ++++++++------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/error/mod.rs b/src/error/mod.rs index c94b26d..068f300 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -3,20 +3,19 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Twig base exception +/// Twig generic error use std::fmt::{self, Display}; -use std::convert::Into; #[macro_use] pub mod macros; // use std Error-trait to improve cross-crate compatibility // don't mix it up with Err(X) -pub use std::error::Error as StdError; +pub use std::error; pub mod api; -// generic wrapper around some StdError - adds location support +// generic wrapper around some ErrorCode - adds location support #[derive(Debug)] pub struct Error where T: api::ErrorCode @@ -28,7 +27,7 @@ pub struct Error // I decided to call this field `code` instead of `error` to not confuse it with the Error trait location: Location, // chaining is required by std::error::Error - cause: Option>, + cause: Option>, } impl Error @@ -51,7 +50,7 @@ impl Error &self.location } - pub fn caused_by(mut self, cause: X) -> Self { + pub fn caused_by(mut self, cause: X) -> Self { self.cause = Some(Box::new(cause)); self @@ -71,7 +70,7 @@ impl Error } } -impl StdError for Error +impl error::Error for Error where T: api::ErrorCode { fn description(&self) -> &str { @@ -79,27 +78,18 @@ impl StdError for Error &self.code.description() } - fn cause<'a>(&'a self) -> Option<&'a StdError> { + fn cause<'a>(&'a self) -> Option<&'a error::Error> { // dereference from Option> to Option<&T> self.cause.as_ref().map(|x| &**x) } } -// Error -> Err(Error) -impl Into>> for Error - where T: api::ErrorCode -{ - fn into (self) -> Result> { - Err(self) - } -} - pub struct ErrorIter<'a> { - next: Option<&'a StdError> + next: Option<&'a error::Error> } impl<'a> Iterator for ErrorIter<'a> { - type Item = &'a StdError; + type Item = &'a error::Error; fn next(&mut self) -> Option { return match self.next { diff --git a/src/test_error/error_codes.rs b/src/test_error/error_codes.rs index be94437..7b5b0f9 100644 --- a/src/test_error/error_codes.rs +++ b/src/test_error/error_codes.rs @@ -22,7 +22,7 @@ impl GeneralizeTo for RuntimeErrorCode { } } -#[derive(Clone, Debug)] +#[derive(Debug)] pub enum EngineErrorCode { TemplateNotFound { name: String, search_paths: Vec }, RuntimeError @@ -53,11 +53,27 @@ impl Display for EngineErrorCode { } } -#[derive(Clone, Debug)] +// Just for demonstration +// - lexer and parser could define some objects to indicate their current position +// .. however we call that +#[derive(Debug)] +pub struct MockCursor; + +impl Display for MockCursor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "`{template_name}` line {line} column {column}", + template_name = "index.twig.html", + line = 11, + column = 2) + } +} +// + +#[derive(Debug)] /// Runtime error message. pub enum RuntimeErrorCode { /// Callable invoked with argument count that does not match defined count. - InvalidArgumentCount { defined: usize, given: usize }, + InvalidArgumentCount { defined: usize, given: usize, cursor: MockCursor }, } impl ErrorCode for RuntimeErrorCode { @@ -71,8 +87,11 @@ impl ErrorCode for RuntimeErrorCode { impl Display for RuntimeErrorCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - RuntimeErrorCode::InvalidArgumentCount { ref defined, ref given } => { - write!(f, "Target requires {} arguments, called with {}", defined, given) + RuntimeErrorCode::InvalidArgumentCount { ref defined, ref given, ref cursor } => { + write!(f, "Target requires {defined} arguments, called with {given} for {cursor}", + defined = defined, + given = given, + cursor = cursor) } } } diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs index 550e0a2..f4732ac 100644 --- a/src/test_error/mod.rs +++ b/src/test_error/mod.rs @@ -6,7 +6,7 @@ /// Twig library for rust mod error_codes; -use self::error_codes::{EngineResult, EngineErrorCode, RuntimeResult, RuntimeErrorCode}; +use self::error_codes::{EngineResult, EngineErrorCode, RuntimeResult, RuntimeErrorCode, MockCursor}; use error::api::ErrorCode; fn foo_engine_error() -> EngineResult<()> { @@ -19,7 +19,8 @@ fn foo_engine_error() -> EngineResult<()> { fn foo_runtime_error() -> RuntimeResult<()> { err!(RuntimeErrorCode::InvalidArgumentCount{ defined: 10, - given: 4 + given: 4, + cursor: MockCursor // <-- information about current lexer position and template name }) } @@ -39,16 +40,16 @@ fn test() { .caused_by(foo_runtime_error().unwrap_err()); assert_eq!(my_error.to_string(), - "Some runtime error at src/test_error/mod.rs:38:12\n \ - - caused by: Target requires 10 arguments, called with 4 at src/test_error/mod.rs:20:4\n"); + "Some runtime error at src/test_error/mod.rs:39:12\n \ + - caused by: Target requires 10 arguments, called with 4 for `index.twig.html` line 11 column 2 at src/test_error/mod.rs:20:4\n"); assert_eq!(foo_engine_error().unwrap_err().to_string(), "Template \"hello_world.html.twig\" not found, looked in [] at src/test_error/mod.rs:13:4\n"); assert_eq!(foo_runtime_error().unwrap_err().to_string(), - "Target requires 10 arguments, called with 4 at src/test_error/mod.rs:20:4\n"); + "Target requires 10 arguments, called with 4 for `index.twig.html` line 11 column 2 at src/test_error/mod.rs:20:4\n"); assert_eq!(foo_chain_error().unwrap_err().to_string(), - "Some runtime error at src/test_error/mod.rs:27:19\n \ - - caused by: Target requires 10 arguments, called with 4 at src/test_error/mod.rs:20:4\n"); + "Some runtime error at src/test_error/mod.rs:28:19\n \ + - caused by: Target requires 10 arguments, called with 4 for `index.twig.html` line 11 column 2 at src/test_error/mod.rs:20:4\n"); } From a72887a7dbbf73c3e3a8cd0a7cd0318cb601f128 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Sun, 29 Nov 2015 00:28:27 +0100 Subject: [PATCH 6/8] minor cleanup: error handling --- src/error/api.rs | 8 ++++++++ src/error/macros.rs | 6 +++--- src/error/mod.rs | 3 +-- src/test_error/mod.rs | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/error/api.rs b/src/error/api.rs index 2f75d25..6d4ddaa 100644 --- a/src/error/api.rs +++ b/src/error/api.rs @@ -34,3 +34,11 @@ pub trait ErrorCode: Debug + Display + Any { pub trait GeneralizeTo { fn generalize(&self) -> T; } + +// implement this trait for complex objects like lexer/parser Jobs +// to be able to embedd their most important data in ErrorCodes via `X::dump()` +pub trait Dump { + type Data: Debug + Display; + + fn dump(&self) -> Self::Data; +} diff --git a/src/error/macros.rs b/src/error/macros.rs index 0bb26c3..4d869ba 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -11,12 +11,12 @@ pub use super::*; #[macro_export] macro_rules! err { ( $code:expr ) => ({ - Err(::error::Error::new($code, location!())) + Err(::error::Error::new($code, loc!())) }); } #[macro_export] -macro_rules! location { +macro_rules! loc { () => ({ ::error::Location { module_path : module_path!(), @@ -43,7 +43,7 @@ macro_rules! try_chain { Err(cause) => { let code = ::error::api::GeneralizeTo::generalize(cause.code()); - return Err(::error::Error::new(code, location!()) + return Err(::error::Error::new(code, loc!()) .caused_by(cause) .into()) } diff --git a/src/error/mod.rs b/src/error/mod.rs index 068f300..57839f6 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -6,13 +6,12 @@ /// Twig generic error use std::fmt::{self, Display}; - +use std::error; #[macro_use] pub mod macros; // use std Error-trait to improve cross-crate compatibility // don't mix it up with Err(X) -pub use std::error; pub mod api; // generic wrapper around some ErrorCode - adds location support diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs index f4732ac..6ebe339 100644 --- a/src/test_error/mod.rs +++ b/src/test_error/mod.rs @@ -36,7 +36,7 @@ fn test() { // -> just for demonstration right now: let my_error = EngineErrorCode::RuntimeError - .at(location!()) + .at(loc!()) .caused_by(foo_runtime_error().unwrap_err()); assert_eq!(my_error.to_string(), From a7294975dc8995a6d35c5e91d64363cddc339936 Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Sun, 29 Nov 2015 14:37:29 +0100 Subject: [PATCH 7/8] finalizing error handling + documentation --- src/error/api.rs | 44 ---------------- src/error/macros.rs | 90 ++++++++++++++++++++++++------- src/error/mod.rs | 99 +++++++++++++++++++++++++++++------ src/lib.rs | 55 ++++++++++++++++++- src/test_error/error_codes.rs | 3 +- src/test_error/mod.rs | 2 +- 6 files changed, 212 insertions(+), 81 deletions(-) delete mode 100644 src/error/api.rs diff --git a/src/error/api.rs b/src/error/api.rs deleted file mode 100644 index 6d4ddaa..0000000 --- a/src/error/api.rs +++ /dev/null @@ -1,44 +0,0 @@ -// This file is part of Twig (ported to Rust). -// -// For the copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -/// Twig Error API for error codes - -use error::{Error, Location}; -use std::fmt::{Debug, Display}; -use std::any::Any; - -/// Base functionality for error codes -/// - similar to std::error::Error, but without error-chaining! -/// -/// -pub trait ErrorCode: Debug + Display + Any { - /// A short description of the error code. - /// - /// The description should not contain newlines or sentence-ending - /// punctuation, to facilitate embedding in larger user-facing - /// strings. - fn description(&self) -> &str; - - /// Provide the location, where the error occured. - fn at(self, location: Location) -> Error where - Self: Sized - { - Error::new(self, location) - } -} - -// will be used to transform error codes! -// can't use Into-trait - because we only have references -pub trait GeneralizeTo { - fn generalize(&self) -> T; -} - -// implement this trait for complex objects like lexer/parser Jobs -// to be able to embedd their most important data in ErrorCodes via `X::dump()` -pub trait Dump { - type Data: Debug + Display; - - fn dump(&self) -> Self::Data; -} diff --git a/src/error/macros.rs b/src/error/macros.rs index 4d869ba..68f02b7 100644 --- a/src/error/macros.rs +++ b/src/error/macros.rs @@ -3,22 +3,71 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Twig macro for error handling - -pub use super::*; - +//! Twig macros for error handling +/// A macro which creates a error for the location from which it was invoked. +/// For internal use within the twig library. +/// +/// The expanded expression has type `Result<_,twig::error::Error>`, where the suplied +/// error code must implement `twig::error::api::ErrorCode`. +/// +/// # Examples +/// +/// ```rust,macro_test +/// # #[macro_use] extern crate twig; +/// # fn main() { +/// use std::fmt; +/// use twig::error::{Error, ErrorCode}; +/// +/// #[derive(Debug)] +/// struct MyErrorCode; +/// type MyError = Error; +/// +/// impl ErrorCode for MyErrorCode { +/// fn description(&self) -> &str { "critical error" } +/// } +/// +/// impl fmt::Display for MyErrorCode { +/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +/// write!(f, "{} with human readable details", self.description()) +/// } +/// } +/// +/// let result: Result<(), MyError> = err!(MyErrorCode); +/// if let Err(error) = result { +/// assert_eq!(error.to_string(), "critical error with human readable details at :20:34\n"); +/// } +/// # } +/// ``` #[macro_export] macro_rules! err { ( $code:expr ) => ({ - Err(::error::Error::new($code, loc!())) + Err($crate::error::Error::new($code, loc!())) }); } - +/// A macro which expands to the location from which it was invoked. +/// For internal use within the twig library. +/// +/// The expanded expression has type `twig::error::Location`, and the returned location +/// is not the invocation of the `loc!()` macro itself, but rather the +/// first macro invocation leading up to the invocation of the `loc!()` +/// macro. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate twig; +/// # fn main() { +/// use twig::error; +/// +/// let this_location = loc!(); +/// println!("called from: {}", this_location); +/// # } +/// ``` #[macro_export] macro_rules! loc { () => ({ - ::error::Location { + $crate::error::Location { module_path : module_path!(), filename : file!(), line : line!(), @@ -27,23 +76,28 @@ macro_rules! loc { }); } -// `try_chain!`-macro will create an error-chain with location for each chaining-operation -// -// use as follows -// -// fn foo() -> Result<(), Exception> { -// let result_B: Result<(), Exception> = ...; -// -// try_chain!(result_B); -// } +/// A macro which will create an error-chain with location for each chaining-operation. +/// For internal use within the twig library. +/// +/// `try_chain!` is supposed to be used, whenever errors cross a logic boundary. The trait +/// `twig::error::api::GeneralizeTo` must be implented for `CODE_B`, then use it as follows (pseudo-code) +/// +/// ```ignore +/// fn foo() -> Result<(), Exception> { +/// let result_B: Result<(), Exception> = ...; +/// +/// try_chain!(result_B); // try! would fail here, and +/// } +/// ``` +#[macro_export] macro_rules! try_chain { ( $result:expr ) => ( match $result { Ok(value) => value, Err(cause) => { - let code = ::error::api::GeneralizeTo::generalize(cause.code()); + let code = $crate::error::GeneralizeTo::generalize(cause.code()); - return Err(::error::Error::new(code, loc!()) + return Err($crate::error::Error::new(code, loc!()) .caused_by(cause) .into()) } diff --git a/src/error/mod.rs b/src/error/mod.rs index 57839f6..388aba5 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -3,21 +3,72 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Twig generic error +//! Twig Error Handling -use std::fmt::{self, Display}; +use std::fmt::{self, Debug, Display}; use std::error; +use std::any::Any; #[macro_use] -pub mod macros; +mod macros; // use std Error-trait to improve cross-crate compatibility // don't mix it up with Err(X) -pub mod api; -// generic wrapper around some ErrorCode - adds location support + +/// Lightweight base functionality for error codes +/// +/// Similar to std::error::Error, but _without_ error-chaining. +pub trait ErrorCode: Debug + Display + Any { + /// A short description of the error code. + /// + /// The description should not contain newlines or sentence-ending + /// punctuation, to facilitate embedding in larger user-facing + /// strings. + fn description(&self) -> &str; + + /// Returns generic twig error for this error code. + /// You must provide the location, where the error occured. + fn at(self, location: Location) -> Error where + Self: Sized + { + Error::new(self, location) + } +} + +/// Reference implementation to make examples easier. +impl ErrorCode for &'static str { + fn description(&self) -> &str { *self } +} + +/// Transform ErrorCodes +/// +/// It is different to the Into-trait, because we only take references. +pub trait GeneralizeTo { + fn generalize(&self) -> T; +} + +/// Record current state of complex objects +/// +/// Implement this trait for complex objects. Make sure, that the Dump::Data type +/// does not contain lifetimes to keep error codes simple. In practice this means +/// cloning all referenced data into the dump. +/// +/// For any `X: Dump` you can +/// +/// * reference the associated type via `::Data` +/// * create the dump via `X.dump()` +pub trait Dump { + type Data: Debug + Display + 'static; + + fn dump(&self) -> Self::Data; +} + +/// Generic twig error +/// +/// Wrapper around some `ErrorCode` - adds location support and error-chaining. #[derive(Debug)] pub struct Error - where T: api::ErrorCode + where T: ErrorCode { // the exception codes are going to be enums // - i.e. Exception implements std::error::Error without any boilerplate @@ -30,8 +81,18 @@ pub struct Error } impl Error - where T: api::ErrorCode + where T: ErrorCode { + /// Create a new twig error out of some generic error code. + /// + /// # Examples + /// + /// ``` + /// # #[macro_use] extern crate twig; + /// # fn main() { + /// Error::new("my error", loc!()); // shorthand: `err!("my error")` + /// # } + /// ``` pub fn new(code: T, location: Location) -> Error { Error { code: code, @@ -40,28 +101,32 @@ impl Error } } + /// Return the associated error code. #[allow(dead_code)] // only used by tests pub fn code(&self) -> &T { &self.code } + /// Return the location the error occured. pub fn location(&self) -> &Location { &self.location } + /// Set the cause for this error. pub fn caused_by(mut self, cause: X) -> Self { self.cause = Some(Box::new(cause)); self } + /// Wraps this error inside another error as its cause. pub fn causes(self, wrapper: Error) -> Error where - X: api::ErrorCode + X: ErrorCode { wrapper.caused_by(self) } - // iterate along the error-chain. + /// Creates an iterator to iterate along the error cause-chain. pub fn iter(&self) -> ErrorIter { ErrorIter { next: Some(self), @@ -70,7 +135,7 @@ impl Error } impl error::Error for Error - where T: api::ErrorCode + where T: ErrorCode { fn description(&self) -> &str { // delegate the error description to the ErrorCode @@ -83,6 +148,7 @@ impl error::Error for Error } } +/// Iterator to iterate along the error cause-chain. pub struct ErrorIter<'a> { next: Option<&'a error::Error> } @@ -102,7 +168,7 @@ impl<'a> Iterator for ErrorIter<'a> { } impl Display for Error - where T: api::ErrorCode + where T: ErrorCode { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { try!(write!(f, "{error_code} at {location}\n", @@ -116,12 +182,15 @@ impl Display for Error } } +/// Location in rust source code +/// +/// The Display trait is formatted like `"{filename}:{line}:{column}"`. +/// +/// We just store everything we can get, to identify source code locations. Note that +/// the module_path is currently only displayed in Debug output due to this redundancy. +/// You can access all fields directly. #[derive(Debug)] pub struct Location { - // this might be a bit redundant - but we just store everything we can get. - // we don't need to be super performant on exceptions - because we try to avoid them :-) - // - // note that the module_path is currently only displayed in Debug output due to this redundancy pub module_path : &'static str, // e.g. twig::lexer::job::state::shared_traits pub filename : &'static str, // e.g. /src/lexer/job/state/shared_traits.rs pub line : u32, diff --git a/src/lib.rs b/src/lib.rs index cb72513..6fba888 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,60 @@ // For the copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Twig library for rust +//! # Twig Templating for Rust +//! +//! This library is derived from [Twig (for PHP)][twigphp] and intended to become a _fully compatible_ port - as far as it makes sense. By design Twig is +//! +//! * flexible +//! * fast +//! * and secure +//! +//! Twig Rust will be a template engine for everyone writing web applications with Rust. +//! +//! > **work in progress** - This library is still in development and not yet ready for use. +//! Take a look at the [CHANGELOG][changelog] for more details. +//! +//! ## Syntax and Semantics +//! +//! Twig uses a syntax similar to the Django and Jinja template languages which inspired the Twig runtime environment. +//! +//! ```html +//! +//! +//! +//! Display a thread of posts +//! +//! +//!

{{ thread.title }}

+//!
    +//! {% for post in thread.posts %} +//!
  • {{ post }}
  • +//! {% endfor %} +//!
+//! {# note: this comment will be ignored #} +//! +//! +//! ``` +//! +//! Take a look at this introduction: [Twig for template designers](http://twig.sensiolabs.org/doc/templates.html). +//! +//! ## Flexible Architecture +//! +//! Twig is designed to be highly extensible: +//! +//! * the Twig compiler only defines *general semantics* and a very flexible *extension mechanism*. +//! * extensions define specific behavior and data transformations (like if-statement, for-loop, escape-filter, multiplication-operator, call-expression, etc.) +//! * extensions are chosen at runtime. +//! * if you don't like the default behavior (like if-statement, or call-expression) or if you are missing some functionality (like helpers for a new target like excel-files), all you need to do is replace, add or change extensions. +//! +//! ## License +//! +//! Twig-Rust is released under the [new BSD license][license] (code and documentation) - as is the original Twig for PHP. +//! +//! [github]: https://github.com/rust-web/twig +//! [license]: https://github.com/rust-web/twig/blob/master/LICENSE +//! [changelog]: https://github.com/rust-web/twig/blob/master/CHANGELOG.md +//! [twigphp]: http://twig.sensiolabs.org/documentation #[macro_use] pub mod error; diff --git a/src/test_error/error_codes.rs b/src/test_error/error_codes.rs index 7b5b0f9..18024ce 100644 --- a/src/test_error/error_codes.rs +++ b/src/test_error/error_codes.rs @@ -5,8 +5,7 @@ /// Twig library for rust -use error::api::{ErrorCode, GeneralizeTo}; -use error::Error; +use error::{Error, ErrorCode, GeneralizeTo}; use std::fmt::{self, Display}; diff --git a/src/test_error/mod.rs b/src/test_error/mod.rs index 6ebe339..5360c96 100644 --- a/src/test_error/mod.rs +++ b/src/test_error/mod.rs @@ -7,7 +7,7 @@ mod error_codes; use self::error_codes::{EngineResult, EngineErrorCode, RuntimeResult, RuntimeErrorCode, MockCursor}; -use error::api::ErrorCode; +use error::ErrorCode; fn foo_engine_error() -> EngineResult<()> { err!(EngineErrorCode::TemplateNotFound { From 984d52cd5cedb8f4869d93a593f25517d6cdad9d Mon Sep 17 00:00:00 2001 From: Colin Kiegel Date: Sun, 29 Nov 2015 14:40:48 +0100 Subject: [PATCH 8/8] fix testcase --- src/error/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/error/mod.rs b/src/error/mod.rs index 388aba5..792f8b3 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -90,6 +90,8 @@ impl Error /// ``` /// # #[macro_use] extern crate twig; /// # fn main() { + /// use twig::error::Error; + /// /// Error::new("my error", loc!()); // shorthand: `err!("my error")` /// # } /// ```