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
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! A composite error type for errors that can occur while interacting with Twitter.
//!
//! Any action that crosses the network to call Twitter has many places where it can go wrong.
//! Whether it's a bad network connection, a revoked authorization token, a deleted tweet, or
//! anything in between, those errors are all represented in the (rather sprawling) [`Error`] enum.
//! Any errors direct from Twitter are represented as a collection of [`TwitterErrorCode`]s,
//! contained in a [`TwitterErrors`] wrapper, and held in the `Error::TwitterError` enum variant.
//! For more information, see the documentation for the [`Error`] enum.
//!
//! [`Error`]: enum.Error.html
//! [`TwitterErrorCode`]: struct.TwitterErrorCode.html
//! [`TwitterErrors`]: struct.TwitterErrors.html

use chrono;
use hyper;
#[cfg(feature = "native_tls")]
use native_tls;
use serde::{Deserialize, Serialize};
use serde_json;
use std::{self, fmt};
use tokio;

use crate::common::Headers;

/// Convenient alias to a Result containing a local Error type
pub type Result<T> = std::result::Result<T, Error>;

///Represents a collection of errors returned from a Twitter API call.
///
///This is returned as part of [`Error::TwitterError`][] whenever Twitter has rejected a call.
///
///[`Error::TwitterError`]: enum.Error.html
#[derive(Debug, Deserialize, Serialize, thiserror::Error)]
pub struct TwitterErrors {
    /// A collection of errors
    pub errors: Vec<TwitterErrorCode>,
}

impl fmt::Display for TwitterErrors {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut first = true;
        for e in &self.errors {
            if first {
                first = false;
            } else {
                writeln!(f, ",")?;
            }

            write!(f, "{}", e)?;
        }

        Ok(())
    }
}

///Represents a specific error returned from a Twitter API call.
#[derive(Debug, Deserialize, Serialize)]
pub struct TwitterErrorCode {
    ///The error message returned by Twitter.
    pub message: String,
    ///The numeric error code returned by Twitter. A list of possible error codes can be found in
    ///the [API documentation][error-codes].
    ///
    ///[error-codes]: https://developer.twitter.com/en/docs/basics/response-codes
    pub code: i32,
}

impl fmt::Display for TwitterErrorCode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "#{}: {}", self.code, self.message)
    }
}

/// Represents an error that can occur during media processing.
#[derive(Debug, Clone, PartialEq, Deserialize, thiserror::Error)]
#[error("Media error {code} ({name}) - {message}")]
pub struct MediaError {
    /// A numeric error code assigned to the error.
    pub code: i32,
    /// A short name given to the error.
    pub name: String,
    /// The full text of the error message.
    pub message: String,
}

/// A set of errors that can occur when interacting with Twitter.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    ///A URL was passed to a shortcut function that didn't match the method being called.
    #[error("URL given did not match API method")]
    BadUrl,
    ///The response from Twitter was formatted incorrectly or in an unexpected manner. The enclosed
    ///values are an explanatory string and, if applicable, the input that caused the error.
    ///
    ///This usually reflects a bug in this library, as it means I'm not parsing input right.
    #[error("Invalid response received: {} ({:?})", _0, _1)]
    InvalidResponse(&'static str, Option<String>),
    ///The response from Twitter was missing an expected value.  The enclosed value was the
    ///expected parameter.
    ///
    ///This usually reflects a bug in this library, as it means I'm expecting a value that may not
    ///always be there, and need to update my parsing to reflect this.
    #[error("Value missing from response: {}", _0)]
    MissingValue(&'static str),
    ///The `Future` being polled has already returned a completed value (or another error). In
    ///order to retry the request, create the `Future` again.
    #[error("Future has already completed")]
    FutureAlreadyCompleted,
    ///The response from Twitter returned an error structure instead of the expected response. The
    ///enclosed value was the response from Twitter.
    #[error("Errors returned by Twitter: {_1}")]
    TwitterError(Headers, TwitterErrors),
    ///The response returned from Twitter contained an error indicating that the rate limit for
    ///that method has been reached. The enclosed value is the Unix timestamp in UTC when the next
    ///rate-limit window will open.
    #[error("Rate limit reached, hold until {}", _0)]
    RateLimit(i32),
    ///An attempt to upload a video or gif successfully uploaded the file, but failed in
    ///post-processing. The enclosed value contains the error message from Twitter.
    #[error("Error processing media: {}", _0)]
    MediaError(#[from] MediaError),
    ///The response from Twitter gave a response code that indicated an error. The enclosed value
    ///was the response code.
    ///
    ///This is only returned if Twitter did not also return an [error code][TwitterErrors] in the
    ///response body. That check is performed before examining the status code.
    ///
    ///[TwitterErrors]: struct.TwitterErrors.html
    #[error("Error status received: {}", _0)]
    BadStatus(hyper::StatusCode),
    ///The web request experienced an error. The enclosed error was returned from hyper.
    #[error("Network error: {}", _0)]
    NetError(#[from] hyper::Error),
    ///The `native_tls` implementation returned an error. The enclosed error was returned from
    ///`native_tls`.
    #[cfg(feature = "native_tls")]
    #[error("TLS error: {}", _0)]
    TlsError(#[from] native_tls::Error),
    ///An error was experienced while processing the response stream. The enclosed error was
    ///returned from libstd.
    #[error("IO error: {}", _0)]
    IOError(#[from] std::io::Error),
    ///An error occurred while loading the JSON response. The enclosed error was returned from
    ///`serde_json`.
    #[error("JSON deserialize error: {}", _0)]
    DeserializeError(#[from] serde_json::Error),
    ///An error occurred when parsing a timestamp from Twitter. The enclosed error was returned
    ///from chrono.
    #[error("Error parsing timestamp: {}", _0)]
    TimestampParseError(#[from] chrono::ParseError),
    ///The tokio `Timer` instance was shut down while waiting on a timer, for example while waiting
    ///for media to be processed by Twitter. The enclosed error was returned from `tokio`.
    #[error("Timer runtime shutdown: {}", _0)]
    TimerShutdownError(#[from] tokio::time::error::Error),
    ///An error occurred when reading the value from a response header. The enclused error was
    ///returned from hyper.
    ///
    ///This error should be considerably rare, but is included to ensure that egg-mode doesn't
    ///panic if it receives malformed headers or the like.
    #[error("Error decoding headers: {}", _0)]
    HeaderParseError(#[from] hyper::header::ToStrError),
    ///An error occurred when converting a rate-limit header to an integer. The enclosed error was
    ///returned from the standard library.
    ///
    ///This error should be considerably rare, but is included to ensure that egg-mode doesn't
    ///panic if it receives malformed headers or the like.
    #[error("Error converting headers: {}", _0)]
    HeaderConvertError(#[from] std::num::ParseIntError),
}