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
use std::collections::HashMap;

use crate::config::Config;
use crate::oauth1;
use crate::params::Params;
use crate::response::Response;
use crate::Error;
use http::Method;
use log::{debug, info};

#[derive(Clone)]
pub struct Requester {
    config: Config,
    base_url: String,
    algorithm: oauth1::Algorithm,
}

impl Requester {
    pub fn new(config: Config, base_url: String) -> Self {
        Self {
            config,
            base_url,
            algorithm: oauth1::Algorithm::Sha256,
        }
    }

    pub fn with_base_url(self, base_url: String) -> Self {
        Self { base_url, ..self }
    }

    pub fn with_algorithm(self, algorithm: oauth1::Algorithm) -> Self {
        Self { algorithm, ..self }
    }

    fn make_url(&self, endpoint: &str) -> String {
        format!("{}/{}", self.base_url, endpoint.trim_start_matches('/'))
    }

    fn auth_header(&self, method: &Method, url: &str, params: &Option<Params>) -> String {
        oauth1::authorize(
            method.as_str(),
            url,
            &self.config.consumer,
            Some(&self.config.token),
            params.clone().map(|p| p.into()),
            Some(&self.config.account),
            self.algorithm,
        )
    }

    pub fn request(
        &self,
        method: Method,
        endpoint: &str,
        params: Option<Params>,
        headers: Option<Params>,
        payload: Option<&str>,
    ) -> Result<Response, Error> {
        let url = self.make_url(endpoint);
        let auth = self.auth_header(&method, &url, &params);

        let mut req = ureq::request(method.as_str(), &url)
            .set("Authorization", &auth)
            .set("Content-Type", "application/json");

        if let Some(params) = params {
            for (k, v) in params.get() {
                req = req.query(k, v);
            }
        }

        if let Some(headers) = headers {
            for (k, v) in headers.get() {
                req = req.set(k, v);
            }
        }

        let resp = if let Some(payload) = payload {
            info!("Payload: {}", payload);
            req.send_string(payload)
        } else {
            req.send_string("")
        };

        let resp = match resp {
            Ok(resp) => resp,
            Err(ureq::Error::Status(code, resp)) => {
                return Err(Error::HttpRequestError(
                    code,
                    resp.into_string().unwrap_or_default(),
                ));
            }
            Err(ureq::Error::Transport(transport)) => {
                return Err(Error::HttpTransportError(transport.to_string()));
            }
        };
        let status = http::StatusCode::from_u16(resp.status()).unwrap();
        let headers: HashMap<String, String> = resp
            .headers_names()
            .into_iter()
            .filter_map(|name| resp.header(&name).map(|value| (name, value.to_owned())))
            .collect();

        let parsed = Response::new(status, headers, resp.into_string()?);
        debug!("Response: {}", &parsed);
        Ok(parsed)
    }
}