mpdclient 0.1.2

Rust interface to MPD using libmpdclient
Documentation
use std::{ffi::CStr, fmt::Display};

use mpdclient_sys::{
    mpd_connection, mpd_connection_get_error_message, mpd_connection_get_server_error,
    mpd_connection_get_system_error, mpd_error_MPD_ERROR_ARGUMENT, mpd_error_MPD_ERROR_CLOSED,
    mpd_error_MPD_ERROR_MALFORMED, mpd_error_MPD_ERROR_OOM, mpd_error_MPD_ERROR_RESOLVER,
    mpd_error_MPD_ERROR_SERVER, mpd_error_MPD_ERROR_STATE, mpd_error_MPD_ERROR_SUCCESS,
    mpd_error_MPD_ERROR_SYSTEM, mpd_error_MPD_ERROR_TIMEOUT, mpd_server_error,
};

pub(crate) type Result<T> = std::result::Result<T, Error>;

/// Collction of all possible errors from the library
#[derive(Debug, thiserror::Error)]
pub enum Error {
    // Lib
    /// Direct translation of an error returned by MPD.
    #[error("MPD Error: {0}")]
    Mpd(String, MpdError),

    /// Direct translation of a Status Error returned by MPD
    #[error("MPD Status error: {0}")]
    Status(String),

    /// Safeguard to not allow the user to set `0` as timeout for a
    /// [`Connection`](crate::Connection) in [`set_timeout()`](crate::Connection::set_timeout()),
    /// as it would panic in the underlying C library.
    #[error("not allowed to set a 0 timeout")]
    TimeoutNull,

    /// Keepalive directive to the `TcpSocket` in
    /// [`set_keepalive()`](crate::Connection::set_keepalive()) failed.
    #[error("MPD keepalive failed")]
    KeepAlive,

    /// MPD returned a different [`Entity`](crate::entity::Entity) type than the library
    /// requested/expected.
    #[error("Unexpected server entity type return")]
    EntityReturnType,

    /// User entered or MPD returned an unknown value for the type.
    #[error("Unknown '{0}' value")]
    Unknown(String),

    /// MPD returned no [`Entity`](crate::entity::Entity), even though one or more should have been
    /// requested.
    #[error("The server hasn't returned an entity, but the client expected one")]
    NoEntity,

    /// MPD returned more than one [`Entity`](crate::entity::Entity), but the request should only yield a
    /// single one.
    #[error("The server has returned multiple entities, but the client expected only one")]
    MultipleEntities,

    /// Rust [`NulError`](std::ffi::NulError).
    #[error(transparent)]
    Nul(#[from] std::ffi::NulError),

    /// Rust [`SystemTimeError`](std::time::SystemTimeError).
    #[error(transparent)]
    SystemTime(#[from] std::time::SystemTimeError),
}

impl Error {
    pub(crate) fn from_mpd(mpd_error: MpdError, connection: *mut mpd_connection) -> Self {
        let msg = unsafe { CStr::from_ptr(mpd_connection_get_error_message(connection)) }
            .to_string_lossy()
            .to_string();
        match mpd_error {
            MpdError::System(err) => Self::Mpd(format!("{msg}: {err}"), mpd_error),
            MpdError::Server(err) => Self::Mpd(format!("{msg}: {err}"), mpd_error),
            _ => Self::Mpd(msg, mpd_error),
        }
    }
}

/// Direct translation of an error returned by MPD.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MpdError {
    /// Process out of memory.
    OutOfMemory,

    /// Function was called with invalid or unrecognized argument.
    Argument,

    /// Function is not available in the current state of libmpdclient.
    State,

    /// Server respone timeout.
    Timeout,

    /// Error from the underlying system.
    System(i32),

    /// Unknown host.
    Resolver,

    /// Malformed response from MPD.
    Malformed,

    /// Connection closed by MPD.
    Closed,

    /// Server returned an error code.
    Server(MpdServerError),
}

impl MpdError {
    pub(crate) fn from_sys(mpd_sys_error: u32, connection: *mut mpd_connection) -> Option<Self> {
        #[allow(non_upper_case_globals)]
        match mpd_sys_error {
            mpd_error_MPD_ERROR_SUCCESS => None,
            mpd_error_MPD_ERROR_OOM => Some(Self::OutOfMemory),
            mpd_error_MPD_ERROR_ARGUMENT => Some(Self::Argument),
            mpd_error_MPD_ERROR_STATE => Some(Self::State),
            mpd_error_MPD_ERROR_TIMEOUT => Some(Self::Timeout),
            mpd_error_MPD_ERROR_SYSTEM => Some(Self::System(unsafe {
                mpd_connection_get_system_error(connection)
            })),
            mpd_error_MPD_ERROR_RESOLVER => Some(Self::Resolver),
            mpd_error_MPD_ERROR_MALFORMED => Some(Self::Malformed),
            mpd_error_MPD_ERROR_CLOSED => Some(Self::Closed),
            mpd_error_MPD_ERROR_SERVER => Some(Self::Server(unsafe {
                MpdServerError::from(mpd_connection_get_server_error(connection))
            })),
            _ => unreachable!(),
        }
    }
}

// missing docs, as there are no real docs in libmpdclient (or the protocol?)
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MpdServerError {
    Unk = -1,

    NotList = 1,
    Arg = 2,
    Password = 3,
    Permission = 4,
    UnknownCmd = 5,

    NoExist = 50,
    PlaylistMax = 51,
    System = 52,
    PlaylistLoad = 53,
    UpdateAlready = 54,
    PlayerSync = 55,
    Exist = 56,
}

impl From<mpd_server_error> for MpdServerError {
    fn from(value: mpd_server_error) -> Self {
        match value {
            -1 => Self::Unk,
            1 => Self::NotList,
            2 => Self::Arg,
            3 => Self::Password,
            4 => Self::Permission,
            5 => Self::UnknownCmd,
            50 => Self::NoExist,
            51 => Self::PlaylistMax,
            52 => Self::System,
            53 => Self::PlaylistLoad,
            54 => Self::UpdateAlready,
            55 => Self::PlayerSync,
            56 => Self::Exist,
            _ => unreachable!(),
        }
    }
}

impl Display for MpdServerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Unk => write!(f, "Unknown"),
            Self::NotList => write!(f, "Not a List"),
            Self::Arg => write!(f, "Argument"),
            Self::Password => write!(f, "Password"),
            Self::Permission => write!(f, "Permission"),
            Self::UnknownCmd => write!(f, "Unknown Command"),
            Self::NoExist => write!(f, "Doesn't exist"),
            Self::PlaylistMax => write!(f, "Playlist Maximum"),
            Self::System => write!(f, "System"),
            Self::PlaylistLoad => write!(f, "Playlist Load"),
            Self::UpdateAlready => write!(f, "Update already"),
            Self::PlayerSync => write!(f, "Player Sync"),
            Self::Exist => write!(f, "Exists"),
        }
    }
}

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

    #[test]
    fn mpd_from_sys_base() {
        let null_connection = std::ptr::null_mut();
        assert_eq!(MpdError::from_sys(0, null_connection), None);
        assert_eq!(
            MpdError::from_sys(1, null_connection),
            Some(MpdError::OutOfMemory)
        );
        assert_eq!(
            MpdError::from_sys(2, null_connection),
            Some(MpdError::Argument)
        );
        assert_eq!(
            MpdError::from_sys(3, null_connection),
            Some(MpdError::State)
        );
        assert_eq!(
            MpdError::from_sys(4, null_connection),
            Some(MpdError::Timeout)
        );
        assert_eq!(
            MpdError::from_sys(6, null_connection),
            Some(MpdError::Resolver)
        );
        assert_eq!(
            MpdError::from_sys(7, null_connection),
            Some(MpdError::Malformed)
        );
        assert_eq!(
            MpdError::from_sys(8, null_connection),
            Some(MpdError::Closed)
        );
    }
}