use std::fmt::{Debug, Display};
#[derive(Debug)]
pub struct Error<Kind>
where
    Kind: Clone + Debug + Display + PartialEq,
{
    pub(crate) kind: Kind,
    pub(crate) source: Option<crate::Error>,
}
impl<Kind> Error<Kind>
where
    Kind: Clone + Debug + Display + PartialEq,
{
    pub(crate) fn new(kind: Kind) -> Self {
        Self { kind, source: None }
    }
    pub(crate) fn with_source<S>(kind: Kind, source: S) -> Self
    where
        S: Into<crate::Error>,
    {
        Self {
            kind,
            source: Some(source.into()),
        }
    }
    pub fn kind(&self) -> Kind {
        self.kind.clone()
    }
}
impl<Kind> Display for Error<Kind>
where
    Kind: Clone + Debug + Display + PartialEq,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(err) = &self.source {
            write!(f, "{}: {}", self.kind, err)
        } else {
            write!(f, "{}", self.kind)
        }
    }
}
impl<Kind> std::error::Error for Error<Kind>
where
    Kind: Clone + Debug + Display + PartialEq,
{
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.source
            .as_ref()
            .map(|boxed| boxed.as_ref() as &(dyn std::error::Error + 'static))
    }
}
impl<Kind> From<Kind> for Error<Kind>
where
    Kind: Clone + Debug + Display + PartialEq,
{
    fn from(kind: Kind) -> Self {
        Self { kind, source: None }
    }
}
#[cfg(test)]
mod test {
    #![allow(dead_code)]
    use super::*;
    use std::fmt::Formatter;
    #[derive(Clone, Debug, PartialEq)]
    enum FooErrorKind {
        Bar,
        Baz,
    }
    impl Display for FooErrorKind {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            match self {
                Self::Bar => write!(f, "bar error"),
                Self::Baz => write!(f, "baz error"),
            }
        }
    }
    type FooError = Error<FooErrorKind>;
    #[test]
    fn new() {
        let error = FooError::new(FooErrorKind::Bar);
        assert_eq!(error.kind, FooErrorKind::Bar);
        assert!(error.source.is_none());
    }
    #[test]
    fn with_source() {
        let source = std::io::Error::new(std::io::ErrorKind::Other, "foo");
        let error = FooError::with_source(FooErrorKind::Bar, source);
        assert_eq!(error.kind, FooErrorKind::Bar);
        assert_eq!(error.source.unwrap().to_string(), "foo");
    }
    #[test]
    fn kind() {
        let error: FooError = FooErrorKind::Bar.into();
        let kind = error.kind();
        let _ = error.kind();
        assert_eq!(kind, FooErrorKind::Bar);
    }
    #[test]
    fn display_without_source() {
        let error: FooError = FooErrorKind::Bar.into();
        assert_eq!(format!("{}", error), "bar error");
    }
    #[test]
    fn from() {
        let error: FooError = FooErrorKind::Bar.into();
        assert_eq!(error.kind, FooErrorKind::Bar);
        assert!(error.source.is_none());
    }
}