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
//! Represents all the errors epressed by the API and library.
//!
//! These are divided into an `APIError` which represents all errors returned by pubproxy.com and
//! `ParamError` which expresses any parameters that were invalid and can't be caught at compile time.

use std::fmt;

use crate::{constants, types::NaiveResponse};

use thiserror::Error;

/// Represents an error with a parameter type.
///
/// Currently the only types that can error are [`LastChecked`][crate::types::LastChecked] and
/// [`TimeToConnect`][crate::types::TimeToConnect] since they are both bounded values which will
/// error if the provided value is out of bounds.
#[derive(Error, Debug, PartialEq)]
pub enum ParamError<T: PartialEq + fmt::Debug> {
    #[error("'{value:?}' is outside bounds: {bounds:?}")]
    OutOfBounds { bounds: (T, T), value: T },
}

impl<T: PartialEq + fmt::Debug> ParamError<T> {
    pub fn out_of_bounds(value: T, bounds: (T, T)) -> Self {
        Self::OutOfBounds { value, bounds }
    }
}

/// Represents all possible errors returned by the API.
///
/// Some variants should be entirely prevented by this library like `Client`, while others are
/// expected from heavy use like `RateLimit` or from being too strict on parameters like `NoProxy`.
#[derive(Error, Debug)]
pub enum ApiError {
    #[error("Client Error ({status}): {text}\n This should be prevented, please raise an issue")]
    Client { status: u16, text: String },

    #[error("Internal Server Error ({status}): {text}")]
    Server { status: u16, text: String },

    #[error("Invalid API key, make sure your key is valid")]
    ApiKey,

    // TODO: mention fetchers from multiple sessions
    #[error(
        "You have exceeded the rate limit. This could be due to multiple programs using the API. \
 If this is not the case then sorry but the API hates you, consider raising an issue."
    )]
    RateLimit,

    #[error("You have exhausted the daily limit of proxies.")]
    DailyLimit,

    #[error("No matching proxies, consider broadening the parameters used")]
    NoProxy,

    #[error("The API returned an unexpected message. Consider raising an issue with the library")]
    Unknown,
}

impl From<NaiveResponse> for ApiError {
    fn from(naive_resp: NaiveResponse) -> Self {
        let NaiveResponse { status, text } = naive_resp;

        // Some known errors get returned with varied `status` codes so match on response text first
        // then add context to unknown status codes
        match Self::from(text.clone()) {
            Self::Unknown => {
                if (400..500).contains(&status) {
                    Self::Client { status, text }
                } else if (500..600).contains(&status) {
                    Self::Server { status, text }
                } else {
                    unreachable!(
                        "Tried creating ApiError from valid response ({}). Please raise an issue \
                         at {}.",
                        status,
                        constants::REPO_URI
                    );
                }
            }
            err => err,
        }
    }
}

const INVALID_API_KEY: &str =
    "Invalid API. Get your API to make unlimited requests at http://pubproxy.com/#premium";
const RATE_LIMIT: &str = "We have to temporarily stop you. You're requesting proxies a little too \
                          fast (2+ requests per second). Get your API to remove this limit at
                          http://pubproxy.com/#premium";
const DAILY_LIMIT: &str = "You reached the maximum 50 requests for today. Get your API to make \
                           unlimited requests at http://pubproxy.com/#premium";
const NO_PROXY: &str = "No proxy";

impl From<String> for ApiError {
    fn from(s: String) -> Self {
        match s.as_str() {
            INVALID_API_KEY => Self::ApiKey,
            RATE_LIMIT => Self::RateLimit,
            DAILY_LIMIT => Self::DailyLimit,
            NO_PROXY => Self::NoProxy,
            _ => Self::Unknown,
        }
    }
}