Version: v0.9.0

Error Handling

Rust is famous for its code’s safety and robustness thanks to its ownership system. And certainly it is. If it compiles, it works. This is true most of the times, but it doesn't prevent our app from failing if an unexpected result is not properly handled. That is to say: unfound routes, forbidden or restrict access errors for example.

Triggering errors

Sooner or later we're probably going to need to display an error page. Imagine for example an unknown visitor who is trying to access to a restricted site or when the database server is down. Or maybe we're just updating our site and we need to show a temporal message if our app is not available.

To return an error page kalgan provides the function kalgan::http:response::error() which can be launched from any of our controllers. Basically this function creates a kalgan::http:response::Response for the HTML template and HTTP status code given.

Let's see an example:

use kalgan::http::{ request::Request, response, response::Response };
use log::error;
use crate::model::user::User;

#[tokio::main]
pub(crate) async fn index(request: &Request) -> Response {
    match User::select_all().await {
        Ok(users) => {
            render!("user/index.html", context!(...))
        }, Err(e) => {
            error!("{}", e);
            response::error(500, "error.html", context!(...))
        }
    }
}

Here we are fetching data from the users table and managing the response for both success and fail results. Notice that we can pass the Tera context same as we do with every other template.

Finally, be aware that currently these are the only error codes supported:

403 HTTP/1.1 403 Forbidden
404 HTTP/1.1 404 Not Found
500 HTTP/1.1 500 Internal Server Error
503 HTTP/1.1 503 Service Unavailable

Custom error pages

By default when an error arises Kalgan displays a special error page with some extra information which might be helpful to discover the root problem.

However this error page is only visible while we're in a development environment ( environment.is_prod: false ). In a production environment ( environment.is_prod: true ) Kalgan will show a minimal and generic error page instead.

For example:

An error occurred :(
HTTP/1.1 404 Not Found.

We can custom these error pages to adapt them to our site, and probably we'll want.

Firstly, we must declare in our settings file the error codes and the controllers that handle the response. For example:

error:
    403: error_controller::error_403
    404: error_controller::error_404
    500: error_controller::error_500
    503: error_controller::error_503
...

Secondly, we must create the controllers which will return the function kalgan::http:response::error(). For example:

use kalgan::http::{
    request::Request,
    response,
    response::Response
};

pub(crate) fn error_403(_request: &Request) -> Response {
    response::error(403, "error.html", context!(
        "error_code" => &403,
        "error_message" => "Forbidden"
    ))
}
pub(crate) fn error_404(_request: &Request) -> Response {
    response::error(404, "error.html", context!(
        "error_code" => &404,
        "error_message" => "Not Found"
    ))
}
pub(crate) fn error_500(_request: &Request) -> Response {
    response::error(500, "error.html", context!(
        "error_code" => &500,
        "error_message" => "Internal Server Error"
    ))
}
pub(crate) fn error_503(_request: &Request) -> Response {
    response::error(503, "error.html", context!(
        "error_code" => &503,
        "error_message" => "Service Unavailable"
    ))
}

Same as we do with every controller, we must remember to declare it in the resolver:

use std::collections::HashMap;

pub(crate) mod error_controller;
...

pub(crate) fn resolver(request: &kalgan::http::request::Request, controller: &str) -> Result<kalgan::http::response::Response, String> {
    match controller {
        "error_controller::error_403" => Ok(error_controller::error_403(request)),
        "error_controller::error_404" => Ok(error_controller::error_404(request)),
        "error_controller::error_500" => Ok(error_controller::error_500(request)),
        "error_controller::error_503" => Ok(error_controller::error_503(request)),
        ...
        _ => Err(format!("Controller {} not found in resolver.", &controller))
    }
}

In the above example we're using the same HTML template for every controller, which receives the error code and the error message. Our template could look as follows:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Error {{ error_code }} - {{ error_message }}</title>
    </head>
    <body>
        <h1>Error {{ error_code }}</h1>
        <h2>{{ error_message }}</h2>
    </body>
</html>

And that's all. :) Notice that these views will be displayed in both dev and prod environments. We might want to disable them while we're developing.

Logging

A lot of stuff is going under the hood on every request. Some issues might arise immediately, others not. While developing, a database error might be detected early, missing translations or calls to unfound static files could take longer. A good logging system is crucial to monitor our app.

Using env_logger

Kalgan uses the logging API provided by log create. We can use another crate, env_logger, as a logger:

...
[dependencies]
...
env_logger = "0.8.4"

It's recommended to initialize the logger as early as possible in our project:

...
fn main() {
    env_logger::init();
    ...
    kalgan::run("app/config", controller::resolver, Some(middleware::resolver));
}

Finally, we must specify a value for the RUST_LOG environment variable when launching the app. For example:

This variable corresponds with the log level messages we want want to show (which correspond with the log::Level enum from the log crate):

  • trace
  • debug
  • info → Recommended in dev environment.
  • warn → Recommended in prod environment.
  • error