Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/error/macros.rs
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())
}
}
)
}
209 changes: 209 additions & 0 deletions src/error/mod.rs
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>
Copy link
Member

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 of Error names here a bit :)

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> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool solution with iterator 👍

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
}
}
66 changes: 66 additions & 0 deletions src/lib.rs
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() {
}
Loading