twapi-reqwest 0.4.0

Twitter OAuth library used by reqwest.
Documentation
use reqwest::{Error, Response};
use serde_json::Value;
use std::collections::HashMap;
use std::time::Duration;
use twapi_oauth::calc_oauth_header;

use crate::build_client;

pub async fn get_bearer_token_response(
    consumer_key: &str,
    consumer_secret: &str,
    timeout_sec: Option<Duration>,
) -> Result<Response, Error> {
    let key = base64::encode(&format!("{}:{}", consumer_key, consumer_secret));
    let client = build_client(timeout_sec);
    client
        .post("https://api.twitter.com/oauth2/token")
        .header(
            "Content-Type",
            "application/x-www-form-urlencoded;charset=UTF-8",
        )
        .header("Authorization", &format!("Basic {}", key))
        .body("grant_type=client_credentials")
        .send()
        .await
}

pub async fn get_bearer_token(
    consumer_key: &str,
    consumer_secret: &str,
    timeout_sec: Option<Duration>,
) -> Result<Option<String>, Error> {
    let json: Value = get_bearer_token_response(consumer_key, consumer_secret, timeout_sec)
        .await?
        .json()
        .await?;
    match json["access_token"].as_str() {
        Some(access_token) => Ok(Some(access_token.to_string())),
        None => Ok(None),
    }
}

pub async fn request_token_response(
    consumer_key: &str,
    consumer_secret: &str,
    oauth_callback: &str,
    x_auth_access_type: Option<&str>,
    timeout_sec: Option<Duration>,
) -> Result<Response, Error> {
    let uri = "https://api.twitter.com/oauth/request_token";
    let mut header_options = vec![("oauth_callback", oauth_callback)];
    if let Some(x_auth_access_type) = x_auth_access_type {
        header_options.push(("x_auth_access_type", x_auth_access_type));
    }
    let signed = calc_oauth_header(
        &format!("{}&", consumer_secret),
        consumer_key,
        &header_options,
        "POST",
        uri,
        &vec![],
    );
    let client = build_client(timeout_sec);
    client
        .post(uri)
        .header("Authorization", &format!("OAuth {}", signed))
        .send()
        .await
}

pub async fn request_token(
    consumer_key: &str,
    consumer_secret: &str,
    oauth_callback: &str,
    x_auth_access_type: Option<&str>,
    timeout_sec: Option<Duration>,
) -> Result<HashMap<String, String>, Error> {
    let response = request_token_response(
        consumer_key,
        consumer_secret,
        oauth_callback,
        x_auth_access_type,
        timeout_sec,
    )
    .await?;
    Ok(parse_oauth_body(response).await)
}

pub async fn access_token_response(
    consumer_key: &str,
    consumer_secret: &str,
    oauth_token: &str,
    oauth_token_secret: &str,
    oauth_verifier: &str,
    timeout_sec: Option<Duration>,
) -> Result<Response, Error> {
    let uri = "https://api.twitter.com/oauth/access_token";
    let signed = calc_oauth_header(
        &format!("{}&{}", consumer_secret, oauth_token_secret),
        consumer_key,
        &vec![
            ("oauth_token", oauth_token),
            ("oauth_verifier", oauth_verifier),
        ],
        "POST",
        uri,
        &vec![],
    );
    let client = build_client(timeout_sec);
    client
        .post(uri)
        .header("Authorization", &format!("OAuth {}", signed))
        .send()
        .await
}

pub async fn access_token(
    consumer_key: &str,
    consumer_secret: &str,
    oauth_token: &str,
    oauth_token_secret: &str,
    oauth_verifier: &str,
    timeout_sec: Option<Duration>,
) -> Result<HashMap<String, String>, Error> {
    let response = access_token_response(
        consumer_key,
        consumer_secret,
        oauth_token,
        oauth_token_secret,
        oauth_verifier,
        timeout_sec,
    )
    .await?;
    Ok(parse_oauth_body(response).await)
}

async fn parse_oauth_body(response: Response) -> HashMap<String, String> {
    let status = response.status();
    let mut result = HashMap::new();
    match response.text().await {
        Ok(body) => {
            if status.is_success() {
                for item in body.split("&") {
                    let mut pair = item.split("=");
                    if let Some(key) = pair.next() {
                        result.insert(key.to_owned(), pair.next().unwrap_or("").to_owned());
                    }
                }
            } else {
                result.insert("twapi_request_body".to_owned(), body.clone());
                result.insert(
                    "twapi_request_status_code".to_owned(),
                    status.as_str().to_owned(),
                );
            }
        }
        Err(_) => {}
    }
    result
}