//! Typed inventory operation failure.

use std::fmt;

use libremetaverse_types::compat::ExternalError;

use crate::Error;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InventoryException {
    message: String,
    inner: Option<ExternalError>,
}

impl InventoryException {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            message: String::new(),
            inner: None,
        })
    }

    pub fn new_with_string(message: String) -> Result<Self, Error> {
        Ok(Self {
            message,
            inner: None,
        })
    }

    pub fn new_with_string_exception(
        message: String,
        inner_exception: ExternalError,
    ) -> Result<Self, Error> {
        Ok(Self {
            message,
            inner: Some(inner_exception),
        })
    }

    #[must_use]
    pub fn message(&self) -> &str {
        &self.message
    }
}

impl fmt::Display for InventoryException {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.message.is_empty() {
            formatter.write_str("inventory operation failed")
        } else {
            formatter.write_str(&self.message)
        }
    }
}

impl std::error::Error for InventoryException {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.inner
            .as_ref()
            .map(|error| error as &(dyn std::error::Error + 'static))
    }
}

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

    #[test]
    fn preserves_message_and_inner_error() {
        let error = InventoryException::new_with_string_exception(
            "missing item".to_owned(),
            ExternalError("not found".to_owned()),
        )
        .unwrap();
        assert_eq!(error.to_string(), "missing item");
        assert_eq!(
            std::error::Error::source(&error).unwrap().to_string(),
            "not found"
        );
    }
}
