//! Error reporting.

use std::io;
use std::path::{PathBuf, StripPrefixError};
use std::{fmt::Display, path::Path};

pub use log::*;
use relative_path::RelativeToError;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum CopyError {
    /// I/O error.
    #[error("I/O error: {0}")]
    IO(#[from] io::Error),
    /// Error in filesystem walking with [ignore].
    #[error("FS walk failed: {0}")]
    Ignore(#[from] ignore::Error),
    /// Error converting relative path.
    #[error("invalid relative path: {0}")]
    RelPath(#[from] RelativeToError),
    /// Error stripping path prefix.
    #[error("path truncation failed: {0}")]
    PathPrefix(#[from] StripPrefixError),
    /// General error
    #[error("{0}")]
    Generic(String),
    /// Attach a path and action to an error.
    #[error("{action} {path}: {error}")]
    PathActionError {
        action: &'static str,
        path: PathBuf,
        error: Box<CopyError>,
    },
}

pub trait ResultNote<R, E> {
    fn with_path_action<P: AsRef<Path>>(self, action: &'static str, path: P) -> Result<R, E>;
}

impl<R, E> ResultNote<R, CopyError> for Result<R, E>
where
    E: Into<CopyError> + Display,
{
    fn with_path_action<P: AsRef<Path>>(
        self,
        action: &'static str,
        path: P,
    ) -> Result<R, CopyError> {
        self.map_err(|e| CopyError::PathActionError {
            action,
            path: path.as_ref().to_path_buf(),
            error: Box::new(e.into()),
        })
    }
}
