selene-core 0.8.2

selene-core is the backend for Selene, a local-first music player
Documentation
use std::fmt::Display;

use serde::Deserialize;
use thiserror::Error;

#[derive(Deserialize)]
#[serde(untagged)]
pub enum LastFmResult<T> {
    Ok(T),
    Err(LastFmError),
}

impl<T> LastFmResult<T> {
    pub(crate) fn into_result(self) -> Result<T, LastFmError> {
        self.into()
    }
}

impl<T> From<LastFmResult<T>> for Result<T, LastFmError> {
    fn from(value: LastFmResult<T>) -> Self {
        match value {
            LastFmResult::Ok(t) => Ok(t),
            LastFmResult::Err(last_fm_error) => Err(last_fm_error),
        }
    }
}

#[derive(Debug, Error, Deserialize)]
pub struct LastFmError {
    #[serde(rename = "error", deserialize_with = "deserialize_error_kind")]
    kind: LastFmErrorKind,
    message: String,
}

impl LastFmError {
    #[must_use] 
    pub fn new(code: i32, message: String) -> Self {
        Self {
            kind: LastFmErrorKind::from_code(code),
            message,
        }
    }
}

impl Display for LastFmError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        format!(
            "error: {} (code: {}) | message: {}",
            self.kind,
            self.kind.to_code(),
            self.message
        )
        .fmt(f)
    }
}

#[derive(Debug, Error, Clone, Copy)]
pub enum LastFmErrorKind {
    #[error("This service does not exist")]
    InvalidService,

    #[error("No method with that name in this package")]
    InvalidMethod,

    #[error("You do not have permissions to access the service")]
    AuthenticationFailed,

    #[error("This service doesn't exist in that format")]
    InvalidFormat,

    #[error("Request is missing a required parameter")]
    InvalidParameters,

    #[error("Invalid resource specified")]
    InvalidResourceSpecified,

    #[error("Something else went wrong")]
    OperationFailed,

    #[error("Invalid session key - Please re-authenticate")]
    InvalidSessionKey,

    #[error("You must be granted a valid key by last.fm")]
    InvalidApiKey,

    #[error("This service is temporarily offline. Try again later.")]
    ServiceOffline,

    #[error("Invalid method signature supplied")]
    InvalidMethodSignature,

    #[error("Token has not been authorized")]
    UnauthorizedToken,

    #[error("Token has expired")]
    ExpiredToken,

    #[error("There was a temporary error processing your request. Please try again")]
    TemporaryError,

    #[error("Access for your account has been suspended, please contact Last.fm")]
    SuspendedApiKey,

    #[error("Your IP has made too many requests in a short period")]
    RateLimitExceeded,

    #[error("Last.fm returned an unknown error code")]
    Unknown(i32),
}

impl LastFmErrorKind {
    #[must_use] 
    pub fn from_code(code: i32) -> Self {
        match code {
            2 => Self::InvalidService,
            3 => Self::InvalidMethod,
            4 => Self::AuthenticationFailed,
            5 => Self::InvalidFormat,
            6 => Self::InvalidParameters,
            7 => Self::InvalidResourceSpecified,
            8 => Self::OperationFailed,
            9 => Self::InvalidSessionKey,
            10 => Self::InvalidApiKey,
            11 => Self::ServiceOffline,
            13 => Self::InvalidMethodSignature,
            14 => Self::UnauthorizedToken,
            15 => Self::ExpiredToken,
            16 => Self::TemporaryError,
            26 => Self::SuspendedApiKey,
            29 => Self::RateLimitExceeded,
            _ => Self::Unknown(code),
        }
    }

    #[must_use] 
    pub fn to_code(&self) -> i32 {
        match self {
            Self::InvalidService => 2,
            Self::InvalidMethod => 3,
            Self::AuthenticationFailed => 4,
            Self::InvalidFormat => 5,
            Self::InvalidParameters => 6,
            Self::InvalidResourceSpecified => 7,
            Self::OperationFailed => 8,
            Self::InvalidSessionKey => 9,
            Self::InvalidApiKey => 10,
            Self::ServiceOffline => 11,
            Self::InvalidMethodSignature => 13,
            Self::UnauthorizedToken => 14,
            Self::ExpiredToken => 15,
            Self::TemporaryError => 16,
            Self::SuspendedApiKey => 26,
            Self::RateLimitExceeded => 29,
            Self::Unknown(value) => *value,
        }
    }
}

fn deserialize_error_kind<'de, D>(deserializer: D) -> Result<LastFmErrorKind, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let code = i32::deserialize(deserializer)?;
    Ok(LastFmErrorKind::from_code(code))
}