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
//! Interface to the imgur API.

#![warn(missing_docs)]

extern crate curl;
extern crate serde;
#[macro_use]
extern crate try_opt;

use serde::json::{self, Value};
use std::fmt;

macro_rules! api_url (
    ($url: expr) => (
        concat!("https://api.imgur.com/3/", $url)
    );
);

/// A handle to the imgur API.
pub struct Handle {
    client_id: String,
}

impl Handle {
    /// Create a new handle.
    ///
    /// # Parameters
    ///
    /// client_id: Client ID required to access the imgur API.
    pub fn new(client_id: String) -> Self {
        Handle {
            client_id: client_id,
        }
    }

    /// Upload image data to imgur.
    ///
    /// # Parameters
    ///
    /// data: The image data to upload.
    ///
    /// # Returns
    ///
    /// UploadInfo on success, UploadError on failure.
    pub fn upload(&self, data: &[u8]) -> Result<UploadInfo, UploadError> {
        use std::io::Cursor;
        let mut handle = curl::http::handle();
        let mut cursor = Cursor::new(data);
        let request = handle.post(api_url!("image"), &mut cursor)
                            .header("Authorization", &format!("Client-ID {}", self.client_id));
        let response = try!(request.exec());
        let text = try!(std::str::from_utf8(response.get_body()));
        Ok(UploadInfo {
            json: try!(json::from_str(text)),
        })
    }
}

/// Information about an uploaded image.
pub struct UploadInfo {
    json: Value,
}

impl UploadInfo {
    /// Returns the link the image was uploaded to, if any.
    pub fn link(&self) -> Option<&str> {
        let data = try_opt!(self.json.find("data"));
        data.find("link").and_then(|v| v.as_string())
    }
}

enum UploadErrorKind {
    CurlErrCode(curl::ErrCode),
    ResponseBodyInvalidUtf8(std::str::Utf8Error),
    ResponseBodyInvalidJson(json::Error),
}

/// Error that can happen on image upload.
pub struct UploadError {
    kind: UploadErrorKind,
}

impl From<curl::ErrCode> for UploadError {
    fn from(src: curl::ErrCode) -> Self {
        UploadError {
            kind: UploadErrorKind::CurlErrCode(src),
        }
    }
}

impl From<std::str::Utf8Error> for UploadError {
    fn from(src: std::str::Utf8Error) -> Self {
        UploadError {
            kind: UploadErrorKind::ResponseBodyInvalidUtf8(src),
        }
    }
}

impl From<json::Error> for UploadError {
    fn from(src: json::Error) -> Self {
        UploadError {
            kind: UploadErrorKind::ResponseBodyInvalidJson(src),
        }
    }
}

impl fmt::Display for UploadError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use UploadErrorKind::*;
        match self.kind {
            CurlErrCode(code) => write!(f, "Curl error code: {}", code),
            ResponseBodyInvalidUtf8(err) => {
                write!(f, "Response body is not valid utf-8: {}", err)
            }
            ResponseBodyInvalidJson(ref err) => {
                write!(f, "Response body is not valid json: {}", err)
            }
        }
    }
}