1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use std::error::Error as StdError;
use std::fmt::{Display, Formatter, Result as FmtResult};
use serde::de::{Deserialize, Deserializer, Visitor};

/// Lastfm API error codes
/// Source: https://www.last.fm/api/errorcodes
#[derive(Debug, PartialEq)]
pub enum ApiErrorKind {
    InvalidService,
    InvalidMethod,
    AuthenticationFailed,
    InvalidFormat,
    InvalidParameters,
    InvalidResource,
    OperationFailed,
    InvalidSessionKey,
    InvalidApiKey,
    ServiceOffline,
    SubscribersOnly,
    InvalidMethodSignature,
    UnauthorizedToken,
    ItemNotAvailableForStreaming,
    ServiceTemporaryUnavailable,
    LoginRequired,
    TrialExpired,
    NotEnoughContent,
    NotEnoughMembers,
    NotEnoughFans,
    NotEnoughNeighbours,
    NoPeakRadio,
    RadioNotFound,
    ApiKeySuspended,
    Deprecated,
    RateLimitExceeded,
    Unknown,
}

impl ApiErrorKind {
    /// Constructs error kind variant from numerical code
    fn from_u64(value: u64) -> ApiErrorKind {
        match value {
            02 => ApiErrorKind::InvalidService,
            03 => ApiErrorKind::InvalidMethod,
            04 => ApiErrorKind::AuthenticationFailed,
            05 => ApiErrorKind::InvalidFormat,
            06 => ApiErrorKind::InvalidParameters,
            07 => ApiErrorKind::InvalidResource,
            08 => ApiErrorKind::OperationFailed,
            09 => ApiErrorKind::InvalidSessionKey,
            10 => ApiErrorKind::InvalidApiKey,
            11 => ApiErrorKind::ServiceOffline,
            12 => ApiErrorKind::SubscribersOnly,
            13 => ApiErrorKind::InvalidMethodSignature,
            14 => ApiErrorKind::UnauthorizedToken,
            15 => ApiErrorKind::ItemNotAvailableForStreaming,
            16 => ApiErrorKind::ServiceTemporaryUnavailable,
            17 => ApiErrorKind::LoginRequired,
            18 => ApiErrorKind::TrialExpired,
            20 => ApiErrorKind::NotEnoughContent,
            21 => ApiErrorKind::NotEnoughMembers,
            22 => ApiErrorKind::NotEnoughFans,
            23 => ApiErrorKind::NotEnoughNeighbours,
            24 => ApiErrorKind::NoPeakRadio,
            25 => ApiErrorKind::RadioNotFound,
            26 => ApiErrorKind::ApiKeySuspended,
            27 => ApiErrorKind::Deprecated,
            29 => ApiErrorKind::RateLimitExceeded,
            _ => ApiErrorKind::Unknown,
        }
    }

    /// Returns error code description
    pub fn description(&self) -> &'static str {
        match *self {
            ApiErrorKind::InvalidService => "This service does not exist",
            ApiErrorKind::InvalidMethod => "No method with that name in the package",
            ApiErrorKind::AuthenticationFailed => {
                "You do not have permissions to access the service"
            }
            ApiErrorKind::InvalidFormat => "This service doesn't exist in that format",
            ApiErrorKind::InvalidParameters => "Your request is missing a required parameter",
            ApiErrorKind::InvalidResource => "Invalid resource specified",
            ApiErrorKind::OperationFailed => {
                "Most likely the backend service failed. Please try again."
            }
            ApiErrorKind::InvalidSessionKey => "Please re-authenticate",
            ApiErrorKind::InvalidApiKey => "You must be granted a valid key by last.fm",
            ApiErrorKind::ServiceOffline => "This service is temporarily offline. Try again later.",
            ApiErrorKind::SubscribersOnly => {
                "This station is only available to paid last.fm subscribers"
            }
            ApiErrorKind::InvalidMethodSignature => "Invalid method signature supplied",
            ApiErrorKind::UnauthorizedToken => "This token has not been authorized",
            ApiErrorKind::ItemNotAvailableForStreaming => {
                "This item is not available for streaming"
            }
            ApiErrorKind::ServiceTemporaryUnavailable => {
                "The service is temporarily unavailable, please try again"
            }
            ApiErrorKind::LoginRequired => "User requires to be logged in",
            ApiErrorKind::TrialExpired => {
                "This user has no free radio plays left. Subscription required."
            }
            ApiErrorKind::NotEnoughContent => "There is not enough content to play this station",
            ApiErrorKind::NotEnoughMembers => "This group does not have enough members for radio",
            ApiErrorKind::NotEnoughFans => "This artist does not have enough fans for for radio",
            ApiErrorKind::NotEnoughNeighbours => "There are not enough neighbours for radio",
            ApiErrorKind::NoPeakRadio => {
                "This user is not allowed to listen to radio during peak usage"
            }
            ApiErrorKind::RadioNotFound => "Radio station not found",
            ApiErrorKind::ApiKeySuspended => {
                "This application is not allowed to make requests to the web services"
            }
            ApiErrorKind::Deprecated => "This type of request is no longer supported",
            ApiErrorKind::RateLimitExceeded => {
                "Your IP has made too many requests in a short period, exceeding our API guidelines"
            }
            _ => "This error code is not covered by official API reference",
        }
    }
}

impl<'de> Deserialize<'de> for ApiErrorKind {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ErrorKindVisitor;
        impl<'de> Visitor<'de> for ErrorKindVisitor {
            type Value = ApiErrorKind;

            fn expecting(&self, f: &mut Formatter) -> FmtResult {
                write!(f, "u64 value described by API reference")
            }

            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
            where
                E: StdError,
            {
                let kind = ApiErrorKind::from_u64(v);
                Ok(kind)
            }
        }
        deserializer.deserialize_u64(ErrorKindVisitor)
    }
}

impl Display for ApiErrorKind {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        write!(f, "{}", self.description())
    }
}

#[derive(Deserialize, Debug)]
pub struct ApiError {
    pub error: ApiErrorKind,
    pub message: String, 
    // links omitted for now
}

impl ApiError {
    pub fn description(&self) -> &str {
        self.error.description()
    }
}

impl Display for ApiError {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        write!(
            f,
            "Error kind: {}\nMessage: {}",
            self.error.description(),
            self.message
        )
    }
}