Error Handling

Table of Contents

1 The Error Type

The Rust idiomatic approach to handle errors is to rely on the Result type provided by the standard library. The introduction of the ? operator, which allows to seamlessly shortcut a computation which may fails (of type A -> Result<B, E>) in case one of its “subcomputation” fails is really convenient.

We define a custom Error enum, with the following variants.

ConfigurationParsing PathBuf, toml::de::Error There is a parsing error inside a configuration file
ConfigurationNotFound   There is no configuration file neither in current directory nor in any of its parents
ConfigurationRead std::io::Error There is a configuration file, but cleopatra cannot read it
InitWorkingDirectory std::io::Error Cleopatra could not set-up its working directory
UnknownSubcommand String Cleopatra has been called with an incorrect subcommand
Anomaly String Something else happened, that was not supposed to

All the functions of cleopatra which may fails, either because it relies on such a function (typically, if we interact with the filesystem), shall return a Result<A, cleopatra::error::Error> value.

The correct labeling of errors with a dedicated variant remains a work in progress at the moment. When no suitable variant are available, the ideal action is to add this variant in Error. But in practice, we currently use the Anomaly variant as a catch-all, and there is even a dedicated trait to make it more convenient to “raise” Anomaly errors: Raise

pub trait Raise {
    type In;
    fn or_raise(self, msg : &str) -> Result<Self::In, Error>;

The main idea of the Raise trait is to seamlessy combine functions which may fail (either with the Result<A, E> for any E or the Option<A> type), and to project them in the Result<A, Error> type. We do that by “forgetting” the exact reason why we failed, and just raising an Anomaly with a message that is hopefully clear for the user.

impl<T> Raise for Option<T> {
    type In = T;

    fn or_raise(self, msg : &str) -> Result<T, Error> {

impl<T, E> Raise for Result<T, E> {
    type In = T;

    fn or_raise(self, msg : &str) -> Result<T, Error> {
        self.map_err(|_| Error::Anomaly(String::from(msg)))

2 Formatting Error Messages

To give to the users of cleopatra some feedback about failing executions, we introduce the Message structure, whose fields are the following.

title String A quick summary of the error  
description String A longer message which supposedly provides a solution for the user  

Besides, we add a message method to Error, with the following prototype.

pub fn message(self) -> Message

The implementation of message is simply a case enumeration.

Error::ConfigurationParsing(path, err) =>
    Message {
        title : format!("{:?} is not a valid cleopatra.toml file", path),
        description : format!("The following error was found: {}.", err)
Error::ConfigurationNotFound =>
    Message {
        title : "cleopatra could not file its configuration file".into(),
        description : "You need to add a valid `cleopatra.toml' file at the root of your project.".into()
Error::ConfigurationRead(io) =>
    Message {
        title : "cleopatra could not read its configuration file".into(),
        description : format!("Its attempt failed with the following error: {}.", io)
Error::InitWorkingDirectory(io) =>
    Message {
        title : "cleopatra could not set-up its working directory".into(),
        description : format!("The filesystem reported the following error: {}", io)
Error::UnknownSubcommand(cmd) =>
    Message {
        title : format!("cleopatra has been called with a unsupported subcommand"),
        description : format!(r#"`{}' is not a valid subcommand of cleopatra.
 You can use `cleopatra --help' to get the list of supported subcommands."#, cmd)
Error::Anomaly(msg) =>
    Message {
        title : String::from("An anomaly occured, you probably have found a bug."),
        description : msg

Author: Thomas Letan

Created: 2020-12-20 Sun 12:56