libzettels 0.4.1

A library intended as a backend for applications which implement Niklas Luhmann's system of a 'Zettelkasten'.
Documentation
//Copyright (c) 2020-2022 Stefan Thesing
//
//This file is part of libzettels.
//
//libzettels is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//libzettels is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with libzettels. If not, see http://www.gnu.org/licenses/.

//! Error handling for libzettels.

// --------------------------------------------------------------------------

use std::fmt;
use std::io;
use std::path::PathBuf;
use serde_yaml;
use gitignore;


/// All possible errors that can occur when constructing the index
/// and parsing zettel files.
#[derive(Debug)]
pub enum Error {
    /// This error variant occurs when a link from a zettel file points to a 
    /// file that doesn't exist. This is true for both "followups" specified
    /// in the zettel's YAML-metadata and "links" (inline markdown links).
    /// A `BadLink` contains the following information:
    /// - the "source": path to the Zettel file containing the link
    /// - the erroneous link (as an absolute, albeit non-existent path)
    /// - the underlying 
    /// [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html)
    /// of the `NotFound`-kind.
    BadLink(
        PathBuf,    // source of the link 
        PathBuf,    // link
        io::Error,  // underlying error.
        ),
    /// This error occurs when deserializing of a zettel file fails
    /// because of an erroneous yaml header.
    /// A `BadHeader` contains the following information:
    /// - the path to the Zettel file containing the erronous YAML header
    /// - the unterlying [`serde_yaml::Error`](https://docs.serde.rs/serde_yaml/struct.Error.html).
    BadHeader(
        PathBuf,            // Zettel file with bad header
        serde_yaml::Error,  // underlying yaml error
        ),
    /// This error occurs when something went wrong with applying the ignore
    /// file. It contains the underlying
    /// [`gitignore::Error`](https://nathankleyn.com/gitignore.rs/gitignore/struct.Error.html).
    /// See documentation there for details.
    IgnoreFile(gitignore::Error),
    /// This error occurs for diverse kinds of IO error, like missing files,
    /// wrong permissions etc. It contains the underlying
    /// [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html).
    /// See documentation there for details.
    Io(io::Error),
    /// This error occurs when an attempt to express a path to a file relative
    /// to the root directory of the Zettelkasten failed. It contains the
    /// underlying 
    /// [`std::path::StripPrefixError`](https://doc.rust-lang.org/std/path/struct.StripPrefixError.html).
    /// See documentation there for details.
    NormalizePath(std::path::StripPrefixError),
    /// This error occurs when serializing or deserializing Index
    /// or Config to or from YAML fails. Also when serializeing a Zettel fails. 
    //  It contains the underlying
    /// [`serde_yaml::Error`](https://docs.serde.rs/serde_yaml/struct.Error.html).
    /// See documentation there for details.
    Yaml(serde_yaml::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::BadHeader(ref filepath, ref err) 
                    => display_bad_header(f, filepath, err), 
            Error::BadLink(ref source, ref link, ref _err) 
                    => write!(f, "Bad link from {:?} to\n\
                    {:?},\n\
                    which doesn't exist.", source, link), 
            // For variants which underlying errors already implement 
            // `Display`, so we just use that.
            Error::IgnoreFile(ref err) => write!(f, "{}", err),
            Error::Io(ref err) => write!(f, "{}", err),
            Error::NormalizePath(ref err) => write!(f, "{}", err),
            Error::Yaml(ref err) => write!(f, "{}", err),
        }
    }
}

// ---------------------------------------------------------------------------
// Some Display specifics
// ---------------------------------------------------------------------------

fn display_bad_header(f: &mut fmt::Formatter,
                      filepath: &PathBuf, 
                      yaml_error: &serde_yaml::Error) -> fmt::Result {
    write!(f, "Bad YAML header in {:?}:\n", filepath)?;
    let message = yaml_error.to_string();
    write!(f, "The error reads: \"{}\".\n", yaml_error)?;
    if message.contains("simple key expected") {
        write!(f, "Hint: That usually means that you need to indent the \
        suggested line.")?;
    }
    Ok(())
}

impl std::error::Error for Error {
    //fn cause(&self) -> Option<&std::error::Error> {
    fn cause(&self) -> Option<&dyn std::error::Error> {
        match *self {
            Error::BadHeader(_, ref err) => Some(err),
            Error::BadLink(_, _, ref err) => Some(err),
            // Since we're dealing with vanilla rust errors, they already 
            // implement `Error`, so we just use that.
            Error::IgnoreFile(ref err) => Some(err),
            Error::Io(ref err) => Some(err),
            Error::NormalizePath(ref err) => Some(err),
            Error::Yaml(ref err) => Some(err),
        }
    }
}

impl From<gitignore::Error> for Error {
/// Converts a 
/// [`gitignore::Error`](https://nathankleyn.com/gitignore.rs/gitignore/struct.Error.html) 
/// into a [`libzettels::Error`](enum.Error.html).
    fn from(err: gitignore::Error) -> Error {
        Error::IgnoreFile(err)
    }
}

impl From<io::Error> for Error {
/// Converts a
/// [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html)
/// into a [`libzettels::Error`](enum.Error.html).
    fn from(err: io::Error) -> Error {
        Error::Io(err)
    }
}

impl From<std::path::StripPrefixError> for Error {
/// Converts a
/// [`std::path::StripPrefixError`](https://doc.rust-lang.org/std/path/struct.StripPrefixError.html)
/// into a [`libzettels::Error`](enum.Error.html).
    fn from(err: std::path::StripPrefixError) -> Error {
        Error::NormalizePath(err)
    }
}

impl From<serde_yaml::Error> for Error {
/// Converts a
/// [`serde_yaml::Error`](https://docs.serde.rs/serde_yaml/struct.Error.html)
/// into a [`libzettels::Error`](enum.Error.html).
    fn from(err: serde_yaml::Error) -> Error {
        Error::Yaml(err)
    }
}