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
//!

use crate::constants::Region;

use futures::future::{err, ok};

use futures::Future;
use snafu::Snafu;
use std::string::FromUtf8Error;

macro_rules! assert_matches {
    ($expression:expr, $($pattern:tt)+) => {
        match $expression {
            $($pattern)+ => (),
            ref e => panic!("Assertion failed: `{:?}` does not match `{}`", e, stringify!($($pattern)+))
        }
    };
}

/// Custom error type for an api errors
// TODO: Remove some of the fields and only keep the ones that the client could catch
// Example: rate-limiting, service unavailable
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum ClientError {
    /// Bad request
    #[snafu(display("Got 400: Bad Request"))]
    BadRequest,
    /// Unauthorized
    #[snafu(display("Got 401: Unauthorized"))]
    Unauthorized,
    /// Forbidden
    #[snafu(display("Got 403: Forbidden"))]
    Forbidden,
    /// Data not found
    #[snafu(display("Got 404: Data not found"))]
    DataNotFound,
    /// Method is not allowwed
    #[snafu(display("Got 405: Method not allowed"))]
    MethodNotAllowed,
    /// Unsupported media type
    #[snafu(display("Got 415: Unsupported media type"))]
    UnsupportedMediaType,
    /// This error is returned when you have exceeded your rate limit for an api.
    // TODO: Add rate-limiting
    #[snafu(display("Got 429: Rate limit exceeded. limit: {}", limit))]
    RateLimitExceeded { limit: usize },
    /// Internal server error
    #[snafu(display("Got 500: Internal server error"))]
    InternalServerError,
    /// Bad gateway
    #[snafu(display("Got 502: Bad Gateway"))]
    BadGateway,
    /// This error is returned when the riot api servers are on maintenance.
    /// Please visit https://developer.riotgames.com/api-status/ for more information
    #[snafu(display("Got 503: Service unavailable for region {:?}", region))]
    ServiceUnavailable { region: Region },
    /// Gateway Timeout
    #[snafu(display("Got 504: Gateway timeout"))]
    GatewayTimeout,

    /// Internal url not parsed error
    #[snafu(display("could not parse url"))]
    UrlNotParsed,

    /// Hyper error
    #[snafu(display("hyper errored: {}", source))]
    HyperError { source: hyper::Error },
    /// This error is returned when the user provides no token
    #[snafu(display("Please provide the correct RIOT_API_KEY variable because {}", source))]
    NoToken { source: std::env::VarError },
    /// This error is returned when the user provides malformed token
    #[snafu(display("Provided token {} is not correct riot api token", token))]
    WrongToken { token: String },

    #[snafu(display("Unsupported scheme: {:?}", scheme))]
    UnsupportedScheme { scheme: Option<String> },

    #[snafu(display("Could not make string from ut8: {}", source))]
    FromUTF8Error { source: FromUtf8Error },

    #[snafu(display("Got native tls error: {}", source))]
    NativeTLSError { source: native_tls::Error },

    #[snafu(display("Got io error: {}", source))]
    IOError { source: std::io::Error },
}

impl ClientError {
    pub fn check_status(region: Region, code: u16) -> Result<(), ClientError> {
        use self::ClientError::*;
        match code {
            400 => Err(BadRequest),
            401 => Err(Unauthorized),
            403 => Err(Forbidden),
            404 => Err(DataNotFound),
            405 => Err(MethodNotAllowed),
            415 => Err(UnsupportedMediaType),
            429 => Err(RateLimitExceeded { limit: 0_usize }),
            500 => Err(InternalServerError),
            502 => Err(BadGateway),
            503 => Err(ServiceUnavailable { region }),
            504 => Err(GatewayTimeout),
            _ => Ok(()),
        }
    }
}

#[cfg(test)]
mod api_error_tests {
    use super::*;
    use crate::api::LeagueClient;
    use crate::constants::Region;

    #[test]
    fn returns_correct_status_codes() {
        let lapi = LeagueClient::new(Region::NA).unwrap();
        let bad_r_err = lapi.get_status(400).unwrap_err();
        let unauthorized_err = lapi.get_status(401).unwrap_err();
        let forbidden_err = lapi.get_status(403).unwrap_err();
        let not_found_err = lapi.get_status(404).unwrap_err();
        let method_not_allowed_err = lapi.get_status(405).unwrap_err();
        let unsupported_media_err = lapi.get_status(415).unwrap_err();
        let rate_err = lapi.get_status(429).unwrap_err();
        let internal_err = lapi.get_status(500).unwrap_err();
        let bad_g_err = lapi.get_status(502).unwrap_err();
        let service_err = lapi.get_status(503).unwrap_err();
        let gateway_t_err = lapi.get_status(504).unwrap_err();
        assert_matches!(bad_r_err, ClientError::BadRequest);
        assert_matches!(unauthorized_err, ClientError::Unauthorized);
        assert_matches!(forbidden_err, ClientError::Forbidden);
        assert_matches!(not_found_err, ClientError::DataNotFound);
        assert_matches!(method_not_allowed_err, ClientError::MethodNotAllowed);
        assert_matches!(unsupported_media_err, ClientError::UnsupportedMediaType);
        assert_matches!(rate_err, ClientError::RateLimitExceeded { limit: 0 });
        assert_matches!(internal_err, ClientError::InternalServerError);
        assert_matches!(bad_g_err, ClientError::BadGateway);
        assert_matches!(
            service_err,
            ClientError::ServiceUnavailable { region: Region::NA }
        );
        assert_matches!(gateway_t_err, ClientError::GatewayTimeout)
    }
}