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
use reqwest;
use reqwest::Url;

use crate::errors::{ErrorResponse, PlaidError, Result};

#[derive(Debug, Copy, Clone)]
pub enum Environment {
    Sandbox,
    Development,
    Production,
}

#[derive(Debug, Clone)]
pub struct Client {
    client: reqwest::Client,
    pub client_id: String,
    pub secret: String,
    environment: Environment,
}

impl Client {
    /// Create a plaid client using the supplied credentials.
    ///
    /// * `client_id` - Your Plaid API client_id.
    /// * `secret` - Your Plaid API secret.
    /// * `environment` - The Plaid environment to use.
    pub fn new(client_id: String, secret: String, environment: Environment) -> Client {
        Client {
            client: reqwest::Client::builder().build().unwrap(),
            client_id,
            secret,
            environment,
        }
    }

    /// Create a plaid client using credentials supplied from the environment.
    ///
    /// Credentials must be passed in `PLAID_CLIENT_ID`, `PLAID_SECRET` and `PLAID_ENVIRONMENT` environment variables.
    /// `PLAID_ENVIRONMENT` must be set to `SANDBOX`, `DEVELOPMENT` or `PRODUCTION`.
    pub fn from_env() -> Client {
        let plaid_environment = match &std::env::var("PLAID_ENVIRONMENT")
        .expect("Missing environment variable: PLAID_ENVIRONMENT. Must be set to SANDBOX, DEVELOPMENT or PRODUCTION")
        .to_string().to_lowercase()[..] {
            "SANDBOX" => Environment::Sandbox,
            "DEVELOPMENT" => Environment::Development,
            "PRODUCTION" => Environment::Production,
            s => panic!("Environment variable PLAID_ENVIRONMENT must be set to SANDBOX, DEVELOPMENT or PRODUCTION. Is actually {}", s),
        };

        Client::new(
            std::env::var("PLAID_CLIENT_ID")
                .expect("Missing environment variable: PLAID_CLIENT_ID")
                .to_string(),
            std::env::var("PLAID_SECRET")
                .expect("Missing environment variable: PLAID_SECRET")
                .to_string(),
            plaid_environment,
        )
    }

    pub async fn send_request<T, U>(&self, url: &str, req: &T) -> Result<U>
    where
        T: serde::Serialize,
        U: for<'de> serde::Deserialize<'de>,
    {
        let resp = self
            .client
            .post(self.get_host().join(url).unwrap())
            .json(req)
            .send()
            .await?;
        if resp.status() == reqwest::StatusCode::OK {
            Ok(resp.json().await?)
        } else {
            let status_code = resp.status();
            let err_resp: ErrorResponse = resp.json().await?;
            Err(PlaidError {
                request_id: err_resp.request_id,
                error_type: err_resp.error_type,
                error_code: err_resp.error_code,
                error_message: err_resp.error_message,
                display_message: err_resp.display_message,
                status_code: status_code,
            }
            .into())
        }
    }

    fn get_host(&self) -> Url {
        match self.environment {
            Environment::Sandbox => Url::parse("https://sandbox.plaid.com/").unwrap(),
            Environment::Development => Url::parse("https://development.plaid.com/").unwrap(),
            Environment::Production => Url::parse("https://production.plaid.com/").unwrap(),
        }
    }
}

pub mod tests {
    use super::*;

    pub const SANDBOX_INSTITUTION: &str = "ins_109508";
    pub const SANDBOX_INSTITUTION_QUERY: &str = "Platypus";
    pub const TEST_PRODUCTS: &[&str] = &["auth", "identity", "transactions"];

    pub fn get_test_client() -> Client {
        Client::new(
            std::env::var("PLAID_CLIENT_ID")
                .expect("Missing environment variable: PLAID_CLIENT_ID")
                .to_string(),
            std::env::var("PLAID_SECRET")
                .expect("Missing environment variable: PLAID_SECRET")
                .to_string(),
            Environment::Sandbox,
        )
    }
}