pluggy/
lib.rs

1use hyper::{Body, Client as HyperClient, Method, Request};
2use hyper_tls::HttpsConnector;
3use serde::Serialize;
4
5#[derive(Serialize)]
6#[serde(rename_all = "camelCase")]
7pub struct AuthRequest {
8    client_id: String,
9    client_secret: String,
10    non_expiring: bool,
11}
12
13pub struct Client {
14    client_id: String,
15    client_secret: String,
16    url: String,
17    client: HyperClient<HttpsConnector<hyper::client::connect::HttpConnector>>,
18}
19
20impl Client {
21    pub fn new(client_id: String, client_secret: String) -> Self {
22        let client = HyperClient::builder().build::<_, hyper::Body>(HttpsConnector::new());
23
24        Self {
25            client_id,
26            client_secret,
27            url: "https://api.pluggy.ai".to_string(),
28            client,
29        }
30    }
31
32    pub fn new_from_env() -> Result<Self, Box<dyn std::error::Error>> {
33        let client_id = dotenv::var("PLUGGY_CLIENT_ID")?;
34        let client_secret = dotenv::var("PLUGGY_CLIENT_SECRET")?;
35        let client = Self::new(client_id, client_secret);
36        Ok(client)
37    }
38
39    pub async fn new_from_env_with_api_key() -> Result<(Self, String), Box<dyn std::error::Error>> {
40        let client = Self::new_from_env()?;
41        let connect_token = client.create_api_key().await?;
42        Ok((client, connect_token))
43    }
44
45    async fn create_api_key(&self) -> Result<String, Box<dyn std::error::Error>> {
46        let url = format!("{}/auth", self.url);
47
48        let payload = AuthRequest {
49            client_id: self.client_id.clone(),
50            client_secret: self.client_secret.clone(),
51            non_expiring: false,
52        };
53
54        // Serialize the payload to a JSON string
55        let json_payload = serde_json::to_string(&payload)?;
56
57        // fetch with hyper rs
58        let request = Request::builder()
59            .method(Method::POST)
60            .uri(url)
61            .header("Content-Type", "application/json")
62            .body(Body::from(json_payload))?;
63
64        let response = self.client.request(request).await?;
65        let body = hyper::body::to_bytes(response.into_body()).await?;
66        let body = String::from_utf8(body.to_vec())?;
67        let json: serde_json::Value = serde_json::from_str(&body)?;
68        let api_key = json["apiKey"].as_str();
69
70        match api_key {
71            Some(api_key) => Ok(api_key.to_string()),
72            None => Err("No api key found".into()),
73        }
74    }
75
76    pub async fn create_connect_token(
77        &self,
78        api_key: &str,
79    ) -> Result<String, Box<dyn std::error::Error>> {
80        let url = format!("{}/connect_token", self.url);
81
82        let request = Request::builder()
83            .method(Method::POST)
84            .uri(url)
85            .header("Content-Type", "application/json")
86            .header("Accept", "application/json")
87            .header("X-API-KEY", api_key)
88            .body(Body::empty())?;
89
90        let response = self.client.request(request).await?;
91        let body = hyper::body::to_bytes(response.into_body()).await?;
92        let body = String::from_utf8(body.to_vec())?;
93        let json: serde_json::Value = serde_json::from_str(&body)?;
94        let connect_token = json["accessToken"].as_str();
95
96        match connect_token {
97            Some(connect_token) => Ok(connect_token.to_string()),
98            None => Err("No connect token found".into()),
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn can_instantiate_from_env() {
109        Client::new_from_env().unwrap();
110    }
111
112    #[tokio::test]
113    async fn can_instantiate_from_env_with_api_key() {
114        Client::new_from_env_with_api_key().await.unwrap();
115    }
116
117    #[tokio::test]
118    async fn can_create_connect_token() {
119        let (client, api_key) = Client::new_from_env_with_api_key().await.unwrap();
120        let connect_token = client.create_connect_token(&api_key).await.unwrap();
121        assert_eq!(connect_token.len(), 892);
122    }
123}