pencil 0.1.2

A micro web framework for Rust.
//! This module implements the central application object.

use std::convert::Into;
use std::sync::RwLock;
use std::fmt;
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::path::PathBuf;
use std::net::ToSocketAddrs;

use rustc_serialize::json::Json;
use rustc_serialize::json::ToJson;
use handlebars::Handlebars;
use hyper;
use hyper::method::Method;
use hyper::server::Request as HTTPRequest;
use hyper::server::Response as HTTPResponse;

use types::{
    PencilError,
        PenHTTPError,
        PenUserError,

    UserError,
    PencilResult,
    ViewFunc,
    HTTPErrorHandler,
    UserErrorHandler,
    BeforeRequestFunc,
    AfterRequestFunc,
    TeardownRequestFunc,
};
use wrappers::{
    Request,
    Response,
};
use helpers::{PathBound, send_from_directory};
use config::Config;
use logging;
use serving::run_server;
use routing::{Map, Rule, Matcher};
use testing::PencilClient;
use http_errors::{HTTPError, NotFound, InternalServerError};
use templating::{render_template, render_template_string, load_template};
use module::Module;


/// The pencil type.  It acts as the central application object.  Once it is created it
/// will act as a central registry for the view functions, the URL rules and much more.
pub struct Pencil {
    /// The path where your application locates.
    pub root_path: String,
    /// The name of the application.  By default it's guessed from the root path.
    pub name: String,
    /// The folder with static files that should be served at `static_url_path`.
    /// Defaults to the `"static"` folder in the root path of the application.
    pub static_folder: String,
    /// The url path for the static files on the web, defaults to be `"/static"`.
    pub static_url_path: String,
    /// The folder that contains the templates that should be used for the application.
    /// Defaults to `''templates''` folder in the root path of the application.
    pub template_folder: String,
    /// The configuration for this application.
    pub config: Config,
    /// The Handlebars registry used to load templates and register helpers.
    pub handlebars_registry: RwLock<Box<Handlebars>>,
    /// The url map for this pencil application.
    pub url_map: Map,
    /// All the attached modules in a hashmap by name.
    pub modules: HashMap<String, Module>,
    /// A dictionary of all view functions registered.  The key will be endpoint.
    view_functions: HashMap<String, ViewFunc>,
    before_request_funcs: Vec<BeforeRequestFunc>,
    after_request_funcs: Vec<AfterRequestFunc>,
    teardown_request_funcs: Vec<TeardownRequestFunc>,
    http_error_handlers: HashMap<u16, HTTPErrorHandler>,
    user_error_handlers: HashMap<String, UserErrorHandler>,
}

fn default_config() -> Config {
    let mut config = Config::new();
    config.set("DEBUG", Json::Boolean(false));
    config.set("TESTING", Json::Boolean(false));
    return config;
}

impl Pencil {
    /// Create a new pencil object.  It is passed the root path of your application.
    /// The root path is used to resolve resources from inside it, for more information
    /// about resource loading, see method `open_resource`.
    ///
    /// Usually you create a pencil object in your main function like this:
    ///
    /// ```rust,no_run
    /// use pencil::Pencil;
    ///
    /// fn main() {
    ///     let mut app = Pencil::new("/web/myapp");
    /// }
    /// ```
    pub fn new(root_path: &str) -> Pencil {
        Pencil {
            root_path: root_path.to_string(),
            name: root_path.to_string(),
            static_folder: String::from("static"),
            static_url_path: String::from("/static"),
            template_folder: String::from("templates"),
            config: default_config(),
            handlebars_registry: RwLock::new(Box::new(Handlebars::new())),
            url_map: Map::new(),
            modules: HashMap::new(),
            view_functions: HashMap::new(),
            before_request_funcs: vec![],
            after_request_funcs: vec![],
            teardown_request_funcs: vec![],
            http_error_handlers: HashMap::new(),
            user_error_handlers: HashMap::new(),
        }
    }

    /// The debug flag.  This field is configured from the config
    /// with the `DEBUG` configuration key.  Defaults to `False`.
    pub fn is_debug(&self) -> bool {
        self.config.get_boolean("DEBUG", false)
    }

    /// The testing flag.  This field is configured from the config
    /// with the `TESTING` configuration key.  Defaults to `False`.
    pub fn is_testing(&self) -> bool {
        self.config.get_boolean("TESTING", false)
    }

    /// Set the debug flag.  This field is configured from the config
    /// with the `DEBUG` configuration key.  Set this to `True` to
    /// enable debugging of the application.
    pub fn set_debug(&mut self, flag: bool) {
        self.config.set("DEBUG", Json::Boolean(flag));
    }

    /// Set the testing flag.  This field is configured from the config
    /// with the `TESTING` configuration key.  Set this to `True` to
    /// enable the test mode of the application.
    pub fn set_testing(&mut self, flag: bool) {
        self.config.set("TESTING", Json::Boolean(flag));
    }

    /// Set global log level based on the application's debug flag.
    /// This is only useful for `env_logger` crate users.
    /// On debug mode, this turns on all debug logging.
    pub fn set_log_level(&self) {
        logging::set_log_level(self);
    }

    /// This is used to register a view function for a given URL rule.
    /// Basically this example:
    ///
    /// ```rust,ignore
    /// app.route("/home", &[Get], "home", home);
    /// app.route("/user/<int:user_id>", &[Get], "user", user);
    /// ```
    ///
    /// A rule that listens for `GET` will implicitly listen for `HEAD`.
    ///
    pub fn route<M: Into<Matcher>, N: AsRef<[Method]>>(&mut self, rule: M, methods: N, endpoint: &str, view_func: ViewFunc) {
        self.add_url_rule(rule.into(), methods.as_ref(), endpoint, view_func);
    }

    /// This is a shortcut for `route`, register a view function for
    /// a given URL rule with just `GET` method (implicitly `HEAD`).
    pub fn get<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
        self.route(rule, &[Method::Get], endpoint, view_func);
    }

    /// This is a shortcut for `route`, register a view function for
    /// a given URL rule with just `POST` method.
    pub fn post<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
        self.route(rule, &[Method::Post], endpoint, view_func);
    }

    /// This is a shortcut for `route`, register a view function for
    /// a given URL rule with just `DELETE` method.
    pub fn delete<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
        self.route(rule, &[Method::Delete], endpoint, view_func);
    }

    /// This is a shortcut for `route`, register a view function for
    /// a given URL rule with just `PATCH` method.
    pub fn patch<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
        self.route(rule, &[Method::Patch], endpoint, view_func);
    }

    /// This is a shortcut for `route`, register a view function for
    /// a given URL rule with just `PUT` method.
    pub fn put<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
        self.route(rule, &[Method::Put], endpoint, view_func);
    }

    /// Connects a URL rule.
    pub fn add_url_rule(&mut self, matcher: Matcher, methods: &[Method], endpoint: &str, view_func: ViewFunc) {
        let url_rule = Rule::new(matcher, methods, endpoint);
        self.url_map.add(url_rule);
        self.view_functions.insert(endpoint.to_string(), view_func);
    }

    /// Register a module on the application.
    pub fn register_module(&mut self, module: Module) {
        module.register(self);
    }

    /// Enables static file handling.
    pub fn enable_static_file_handling(&mut self) {
        let mut rule = self.static_url_path.clone();
        rule = rule + "/<path:filename>";
        let rule_str: &str = &rule;
        self.route(rule_str, &[Method::Get], "static", send_app_static_file);
    }

    /// Registers a function to run before each request.
    pub fn before_request(&mut self, f: BeforeRequestFunc) {
        self.before_request_funcs.push(f);
    }

    /// Registers a function to run after each request.  Your function
    /// must take a response object and modify it.
    pub fn after_request(&mut self, f: AfterRequestFunc) {
        self.after_request_funcs.push(f);
    }

    /// Registers a function to run at the end of each request,
    /// regardless of whether there was an error or not.
    pub fn teardown_request(&mut self, f: TeardownRequestFunc) {
        self.teardown_request_funcs.push(f);
    }

    /// Registers a function as one http error handler.
    /// Same to `httperrorhandler`.
    pub fn register_http_error_handler(&mut self, status_code: u16, f: HTTPErrorHandler) {
        self.http_error_handlers.insert(status_code, f);
    }

    /// Registers a function as one user error handler.
    /// Same to `usererrorhandler`.
    pub fn register_user_error_handler(&mut self, error_desc: &str, f: UserErrorHandler) {
        self.user_error_handlers.insert(error_desc.to_string(), f);
    }

    /// Registers a function as one http error handler.  Example:
    ///
    /// ```rust,no_run
    /// use pencil::{Pencil, PencilResult, Response};
    /// use pencil::HTTPError;
    ///
    ///
    /// fn page_not_found(error: HTTPError) -> PencilResult {
    ///     let mut response = Response::from("The page does not exist");
    ///     response.status_code = 404;
    ///     return Ok(response);
    /// }
    ///
    ///
    /// fn main() {
    ///     let mut app = Pencil::new("/web/demo");
    ///     app.httperrorhandler(404, page_not_found);
    /// }
    /// ```
    pub fn httperrorhandler(&mut self, status_code: u16, f: HTTPErrorHandler) {
        self.register_http_error_handler(status_code, f);
    }

    /// Registers a function as one user error handler.  There are two ways to handle
    /// user errors currently, you can do it in your own view like this:
    ///
    /// ```rust,no_run
    /// use pencil::Request;
    /// use pencil::{PencilResult, Response};
    ///
    ///
    /// #[derive(Clone, Copy)]
    /// struct MyErr(isize);
    ///
    ///
    /// fn some_operation() -> Result<isize, MyErr> {
    ///     return Err(MyErr(10));
    /// }
    ///
    ///
    /// fn my_err_handler(_: MyErr) -> PencilResult {
    ///     Ok(Response::from("My err occurred!"))
    /// }
    ///
    ///
    /// fn hello(_: &mut Request) -> PencilResult {
    ///     match some_operation() {
    ///         Ok(_) => Ok(Response::from("Hello!")),
    ///         Err(e) => my_err_handler(e),
    ///     }
    /// }
    /// ```
    ///
    /// The problem with this is that you have to do it in all of your views, it brings
    /// a lot of redundance, so pencil provides another solution, currently I still
    /// haven't got any better idea on how to store user error handlers, this feature is
    /// really just experimental, if you have any good idea, please wake me up.  Here is
    /// one simple example:
    ///
    /// ```rust,no_run
    /// use std::convert;
    ///
    /// use pencil::Request;
    /// use pencil::{Pencil, PencilResult, Response};
    /// use pencil::{PencilError, PenUserError, UserError};
    ///
    ///
    /// #[derive(Clone, Copy)]
    /// pub struct MyErr(isize);
    ///
    /// impl convert::From<MyErr> for PencilError {
    ///     fn from(err: MyErr) -> PencilError {
    ///         let user_error = UserError::new("MyErr");
    ///         return PenUserError(user_error);
    ///     }
    /// }
    ///
    ///
    /// fn my_err_handler(_: UserError) -> PencilResult {
    ///     Ok(Response::from("My err occurred!"))
    /// }
    ///
    ///
    /// fn some_operation() -> Result<String, MyErr> {
    ///     return Err(MyErr(10));
    /// }
    ///
    ///
    /// fn hello(_: &mut Request) -> PencilResult {
    ///     let rv = try!(some_operation());
    ///     return Ok(rv.into());
    /// }
    ///
    ///
    /// fn main() {
    ///     let mut app = Pencil::new("/web/demo");
    ///     // Use error description as key to store handlers, really ugly...
    ///     app.usererrorhandler("MyErr", my_err_handler);
    /// }
    /// ```
    pub fn usererrorhandler(&mut self, error_desc: &str, f: UserErrorHandler) {
        self.register_user_error_handler(error_desc, f);
    }

    /// Creates a test client for this application, you can use it
    /// like this:
    ///
    /// ```ignore
    /// let client = app.test_client();
    /// let response = client.get('/');
    /// assert!(response.code, 200);
    /// ```
    #[allow(dead_code)]
    fn test_client(&self) -> PencilClient {
        PencilClient::new(self)
    }

    /// Called before the actual request dispatching, you can return value
    /// from here and stop the further request handling.
    fn preprocess_request(&self, request: &mut Request) -> Option<PencilResult> {
        if let Some(module) = self.get_module(request.module_name()) {
            for func in &module.before_request_funcs {
                if let Some(result) = func(request) {
                    return Some(result);
                }
            }
        }
        for func in &self.before_request_funcs {
            if let Some(result) = func(request) {
                return Some(result);
            }
        }
        return None;
    }

    /// Does the request dispatching.  Matches the URL and returns the return
    /// value of the view.
    fn dispatch_request(&self, request: &mut Request) -> PencilResult {
        if let Some(ref routing_error) = request.routing_error {
            return Err(PenHTTPError(routing_error.clone()));
        }
        if let Some(default_options_response) = self.make_default_options_response(request) {
            return Ok(default_options_response);
        }
        match self.view_functions.get(&request.endpoint().unwrap()) {
            Some(&view_func) => {
                return view_func(request);
            },
            None => {
                return Err(PenHTTPError(NotFound));
            }
        }
    }

    /// This method is called to create the default `OPTIONS` response.
    fn make_default_options_response(&self, request: &Request) -> Option<Response> {
        match request.path() {
            Some(path) => {
                let url_adapter = self.url_map.bind(path, request.method());
                if let Some(ref rule) = request.url_rule {
                    // if we provide automatic options for this URL and the request
                    // came with the OPTIONS method, reply automatically
                    if rule.provide_automatic_options && request.method() == Method::Options {
                        let mut response = Response::new_empty();
                        response.headers.set(hyper::header::Allow(url_adapter.allowed_methods()));
                        return Some(response);
                    }
                }
            },
            None => {}
        }
        None
    }

    /// Get a module by its name.
    fn get_module(&self, module_name: Option<String>) -> Option<&Module> {
        if let Some(name) = module_name {
            self.modules.get(&name)
        } else {
            None
        }
    }

    /// Modify the response object before it's sent to the HTTP server.
    fn process_response(&self, request: &Request, response: &mut Response) {
        if let Some(module) = self.get_module(request.module_name()) {
            for func in module.after_request_funcs.iter().rev() {
                func(response);
            }
        }
        for func in self.after_request_funcs.iter().rev() {
            func(response);
        }
    }

    /// Called after the actual request dispatching.
    fn do_teardown_request(&self, request: &Request, e: Option<&PencilError>) {
        if let Some(module) = self.get_module(request.module_name()) {
            for func in module.teardown_request_funcs.iter().rev() {
                func(e);
            }
        }
        for func in self.teardown_request_funcs.iter().rev() {
            func(e);
        }
    }

    /// This method is called whenever an error occurs that should be handled.
    fn handle_all_error(&self, request: &Request, e: PencilError) -> PencilResult {
        match e {
            PenHTTPError(e) => self.handle_http_error(request, e),
            PenUserError(e) => self.handle_user_error(request, e),
        }
    }

    /// Handles an User error.
    fn handle_user_error(&self, request: &Request, e: UserError) -> PencilResult {
        if let Some(module) = self.get_module(request.module_name()) {
            if let Some(handler) = module.user_error_handlers.get(&e.desc) {
                return handler(e);
            }
        }
        if let Some(handler) = self.user_error_handlers.get(&e.desc) {
            return handler(e);
        }
        Err(PenUserError(e))
    }

    /// Handles an HTTP error.
    fn handle_http_error(&self, request: &Request, e: HTTPError) -> PencilResult {
        if let Some(module) = self.get_module(request.module_name()) {
            if let Some(handler) = module.http_error_handlers.get(&e.code()) {
                return handler(e);
            }
        }
        if let Some(handler) = self.http_error_handlers.get(&e.code()) {
            return handler(e);
        }
        Ok(e.to_response())
    }

    /// Default error handing that kicks in when an error occurs that is not
    /// handled.
    fn handle_error(&self, request: &Request, e: &PencilError) -> Response {
        self.log_error(request, e);
        let internal_server_error = InternalServerError;
        if let Ok(response) = self.handle_http_error(request, internal_server_error) {
            return response;
        } else {
            let e = InternalServerError;
            return e.to_response();
        }
    }

    /// Logs an error.
    fn log_error(&self, request: &Request, e: &PencilError) {
        match request.path() {
            Some(path) => {
                error!("Error on {} [{}]: {}", path, request.method(), e.description());
            },
            None => {
                error!("Error: {}", e.description());
            }
        }
    }

    /// Dispatches the request and performs request pre and postprocessing
    /// as well as HTTP error handling and User error handling.
    fn full_dispatch_request(&self, request: &mut Request) -> Result<Response, PencilError> {
        let result = match self.preprocess_request(request) {
            Some(result) => result,
            None => self.dispatch_request(request),
        };
        let rv = match result {
            Ok(response) => Ok(response),
            Err(e) => self.handle_all_error(request, e),
        };
        match rv {
            Ok(mut response) => {
                self.process_response(request, &mut response);
                Ok(response)
            },
            Err(e) => Err(e),
        }
    }

    /// Load and compile and register a template.
    pub fn register_template(&mut self, template_name: &str) {
        let registry_write_rv = self.handlebars_registry.write();
        if registry_write_rv.is_err() {
            panic!("Can't write handlebars registry");
        }
        let mut registry = registry_write_rv.unwrap();
        match load_template(self, template_name) {
            Some(source_rv) => {
                match source_rv {
                    Ok(source) => {
                        if let Err(err) = registry.register_template_string(template_name, source) {
                            panic!(format!("Template compile error: {}", err));
                        }
                    },
                    Err(err) => {
                        panic!(format!("Template {} can't be loaded: {}", template_name, err));
                    }
                }
            },
            None => {
                panic!(format!("Template not found: {}", template_name));
            }
        }
    }

    /// We use `handlebars-rs` as template engine.
    /// Renders a template from the template folder with the given context.
    /// The template name is the name of the template to be rendered.
    /// The context is the variables that should be available in the template.
    pub fn render_template<T: ToJson>(&self, template_name: &str, context: &T) -> PencilResult {
        render_template(self, template_name, context)
    }

    /// We use `handlebars-rs` as template engine.
    /// Renders a template from the given template source string
    /// with the given context.
    /// The source is the sourcecode of the template to be rendered.
    /// The context is the variables that should be available in the template.
    pub fn render_template_string<T: ToJson>(&self, source: &str, context: &T) -> PencilResult {
        render_template_string(self, source, context)
    }

    /// The actual application handler.
    pub fn handle_request(&self, request: &mut Request) -> Response {
        request.match_request();
        match self.full_dispatch_request(request) {
            Ok(response) => {
                self.do_teardown_request(request, None);
                return response;
            },
            Err(e) => {
                let response = self.handle_error(request, &e);
                self.do_teardown_request(request, Some(&e));
                return response;
            }
        };
    }

    /// Runs the application on a hyper HTTP server.
    pub fn run<A: ToSocketAddrs>(self, addr: A) {
        run_server(self, addr);
    }
}

impl hyper::server::Handler for Pencil {
    fn handle(&self, req: HTTPRequest, res: HTTPResponse) {
        let mut request = Request::new(self, req);
        let response = self.handle_request(&mut request);
        response.write(request.method(), res);
    }
}

impl PathBound for Pencil {
    fn open_resource(&self, resource: &str) -> File {
        let mut pathbuf = PathBuf::from(&self.root_path);
        pathbuf.push(resource);
        return File::open(&pathbuf.as_path()).unwrap();
    }
}

impl fmt::Display for Pencil {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<Pencil application {}>", self.name)
    }
}

impl fmt::Debug for Pencil {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<Pencil application {}>", self.name)
    }
}

/// View function used internally to send static files from the static folder
/// to the browser.
fn send_app_static_file(request: &mut Request) -> PencilResult {
    let mut static_path = PathBuf::from(&request.app.root_path);
    static_path.push(&request.app.static_folder);
    let static_path_str = static_path.to_str().unwrap();
    let filename = request.view_args.get("filename").unwrap();
    return send_from_directory(static_path_str, filename, false);
}