crapify 0.2.0

Deep-fry your images, and other crimes against pixels.
use std::fmt;
use std::path::PathBuf;

pub enum CrapifyError {
    MissingInput(PathBuf),
    InputIsDirectory(PathBuf),
    NoStages,
    EmptyStage(usize),
    UnknownStage(String),
    UnsupportedFormat(String),
    DecoderError(image::ImageError),
    EncoderError(image::ImageError),
    Io(std::io::Error),
    Clap(clap::Error),
}

impl fmt::Display for CrapifyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::MissingInput(p) => {
                write!(
                    f,
                    "crapify: {}: not where you said it would be.",
                    p.display()
                )
            }
            Self::InputIsDirectory(p) => {
                write!(
                    f,
                    "crapify: cowardly refused to crapify a directory ({}).",
                    p.display()
                )
            }
            Self::NoStages => {
                write!(f, "crapify: no stages given. crapify what, exactly?")
            }
            Self::EmptyStage(i) => {
                write!(f, "crapify: empty stage at position {i}. one '+' too many.")
            }
            Self::UnknownStage(s) => {
                write!(f, "crapify: '{s}': not a crapifier i know about.")
            }
            Self::UnsupportedFormat(s) => {
                write!(
                    f,
                    "crapify: don't know how to encode '{s}'. try .png or .jpg."
                )
            }
            Self::DecoderError(e) => {
                write!(
                    f,
                    "crapify: that file claims to be an image but won't decode: {e}"
                )
            }
            Self::EncoderError(e) => {
                write!(
                    f,
                    "crapify: ruined the pixels, then ruined the encoder: {e}"
                )
            }
            Self::Io(e) => write!(f, "crapify: {e}"),
            Self::Clap(e) => write!(f, "{e}"),
        }
    }
}

impl fmt::Debug for CrapifyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

impl std::error::Error for CrapifyError {}

impl From<clap::Error> for CrapifyError {
    fn from(e: clap::Error) -> Self {
        Self::Clap(e)
    }
}

impl From<std::io::Error> for CrapifyError {
    fn from(e: std::io::Error) -> Self {
        Self::Io(e)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn missing_input_brand_voice() {
        let e = CrapifyError::MissingInput(PathBuf::from("foo.png"));
        assert!(format!("{e}").starts_with("crapify: foo.png:"));
    }

    #[test]
    fn directory_brand_voice() {
        let e = CrapifyError::InputIsDirectory(PathBuf::from("/tmp"));
        let s = format!("{e}");
        assert!(s.starts_with("crapify: cowardly refused to crapify a directory"));
        assert!(s.contains("/tmp"));
    }

    #[test]
    fn no_stages_brand_voice() {
        let e = CrapifyError::NoStages;
        assert!(format!("{e}").starts_with("crapify: no stages given"));
    }

    #[test]
    fn empty_stage_brand_voice() {
        let e = CrapifyError::EmptyStage(2);
        assert!(format!("{e}").contains("empty stage at position 2"));
    }

    #[test]
    fn unknown_stage_brand_voice() {
        let e = CrapifyError::UnknownStage("xerox".into());
        assert!(format!("{e}").contains("'xerox': not a crapifier"));
    }

    #[test]
    fn unsupported_format_brand_voice() {
        let e = CrapifyError::UnsupportedFormat("heic".into());
        assert!(format!("{e}").contains("'heic'"));
    }
}