-
Notifications
You must be signed in to change notification settings - Fork 1
error handling - first draft #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
35ad19a
error handling - first draft
colin-kiegel 0e82528
compact rust-headers
colin-kiegel 6dd2933
minor refactoring for error handling
colin-kiegel 599472d
minor refactoring for error handling II
colin-kiegel 4f86bc8
minor refactoring for error handling III
colin-kiegel a72887a
minor cleanup: error handling
colin-kiegel a729497
finalizing error handling + documentation
colin-kiegel 984d52c
fix testcase
colin-kiegel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// 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 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<T>>`, 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<MyErrorCode>; | ||
/// | ||
/// 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 <anon>:20:34\n"); | ||
/// } | ||
/// # } | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! err { | ||
( $code:expr ) => ({ | ||
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 { | ||
() => ({ | ||
$crate::error::Location { | ||
module_path : module_path!(), | ||
filename : file!(), | ||
line : line!(), | ||
column : column!(), | ||
} | ||
}); | ||
} | ||
|
||
/// 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<CODE_A>` must be implented for `CODE_B`, then use it as follows (pseudo-code) | ||
/// | ||
/// ```ignore | ||
/// fn foo() -> Result<(), Exception<CODE_A>> { | ||
/// let result_B: Result<(), Exception<CODE_B>> = ...; | ||
/// | ||
/// 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 = $crate::error::GeneralizeTo::generalize(cause.code()); | ||
|
||
return Err($crate::error::Error::new(code, loc!()) | ||
.caused_by(cause) | ||
.into()) | ||
} | ||
} | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// 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 Handling | ||
|
||
use std::fmt::{self, Debug, Display}; | ||
use std::error; | ||
use std::any::Any; | ||
|
||
#[macro_use] | ||
mod macros; | ||
// use std Error-trait to improve cross-crate compatibility | ||
// don't mix it up with Err(X) | ||
|
||
|
||
/// 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<Self> 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<T> { | ||
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 `<X as Dump>::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<T> | ||
where T: ErrorCode | ||
{ | ||
// the exception codes are going to be enums | ||
// - i.e. Exception<MY_ENUM> 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<Box<error::Error>>, | ||
} | ||
|
||
impl<T> Error<T> | ||
where T: ErrorCode | ||
{ | ||
/// Create a new twig error out of some generic error code. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # #[macro_use] extern crate twig; | ||
/// # fn main() { | ||
/// use twig::error::Error; | ||
/// | ||
/// Error::new("my error", loc!()); // shorthand: `err!("my error")` | ||
/// # } | ||
/// ``` | ||
pub fn new(code: T, location: Location) -> Error<T> { | ||
Error { | ||
code: code, | ||
location: location, | ||
cause: None | ||
} | ||
} | ||
|
||
/// 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<X: 'static + error::Error>(mut self, cause: X) -> Self { | ||
self.cause = Some(Box::new(cause)); | ||
|
||
self | ||
} | ||
|
||
/// Wraps this error inside another error as its cause. | ||
pub fn causes<X>(self, wrapper: Error<X>) -> Error<X> where | ||
X: ErrorCode | ||
{ | ||
wrapper.caused_by(self) | ||
} | ||
|
||
/// Creates an iterator to iterate along the error cause-chain. | ||
pub fn iter(&self) -> ErrorIter { | ||
ErrorIter { | ||
next: Some(self), | ||
} | ||
} | ||
} | ||
|
||
impl<T> error::Error for Error<T> | ||
where T: ErrorCode | ||
{ | ||
fn description(&self) -> &str { | ||
// delegate the error description to the ErrorCode | ||
&self.code.description() | ||
} | ||
|
||
fn cause<'a>(&'a self) -> Option<&'a error::Error> { | ||
// dereference from Option<Box<T>> to Option<&T> | ||
self.cause.as_ref().map(|x| &**x) | ||
} | ||
} | ||
|
||
/// Iterator to iterate along the error cause-chain. | ||
pub struct ErrorIter<'a> { | ||
next: Option<&'a error::Error> | ||
} | ||
|
||
impl<'a> Iterator for ErrorIter<'a> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool solution with iterator 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thx. :-) |
||
type Item = &'a error::Error; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
return match self.next { | ||
Some(err) => { | ||
self.next = err.cause(); | ||
Some(err) | ||
} | ||
None => None, | ||
} | ||
} | ||
} | ||
|
||
impl<T> Display for Error<T> | ||
where T: ErrorCode | ||
{ | ||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | ||
try!(write!(f, "{error_code} at {location}\n", | ||
error_code = self.code, | ||
location = self.location)); | ||
|
||
match self.cause { | ||
None => Ok(()), | ||
Some(ref cause) => write!(f, " - caused by: {}", cause), | ||
} | ||
} | ||
} | ||
|
||
/// 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 { | ||
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 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) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,69 @@ | ||
// 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 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 | ||
//! <!DOCTYPE html> | ||
//! <html> | ||
//! <head> | ||
//! <title>Display a thread of posts</title> | ||
//! </head> | ||
//! <body> | ||
//! <h1>{{ thread.title }}</h1> | ||
//! <ul> | ||
//! {% for post in thread.posts %} | ||
//! <li>{{ post }}</li> | ||
//! {% endfor %} | ||
//! </ul> | ||
//! {# note: this comment will be ignored #} | ||
//! </body> | ||
//! </html> | ||
//! ``` | ||
//! | ||
//! 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; | ||
|
||
#[cfg(test)] | ||
mod test_error; | ||
|
||
#[test] | ||
fn it_works() { | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe call
ErrorCode
->TwigError
? I understand that we are running ofError
names here a bit :)