mongodb_atlas_admin/
endpoint.rs

1use async_trait::async_trait;
2use digest_auth::AuthContext;
3use http::{
4    header::{AUTHORIZATION, CONTENT_TYPE, WWW_AUTHENTICATE},
5    HeaderValue, Method,
6};
7use reqwest::{RequestBuilder, Response};
8use serde::{Deserialize, Serialize};
9use std::borrow::Cow;
10
11#[async_trait]
12pub trait Endpoint {
13    type Data;
14    type Error: std::error::Error;
15
16    fn method() -> Method;
17
18    fn path(&self) -> Cow<'static, str>;
19
20    fn body(&self) -> Result<Option<Vec<u8>>, Self::Error> {
21        Ok(None)
22    }
23
24    async fn get_data(res: Response) -> Result<Self::Data, Self::Error>;
25}
26
27pub trait EndpointError {}
28
29fn apply_data(request: RequestBuilder, data: Option<Vec<u8>>) -> RequestBuilder {
30    if let Some(vec) = data {
31        request
32            .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
33            .body(vec)
34    } else {
35        request
36    }
37}
38
39pub struct Client {
40    username: String,
41    password: String,
42}
43
44impl Client {
45    pub fn new<U: Into<String>, P: Into<String>>(username: U, password: P) -> Self {
46        Self {
47            username: username.into(),
48            password: password.into(),
49        }
50    }
51
52    pub async fn execute_endpoint<T: Endpoint + 'static>(
53        &self,
54        a: T,
55    ) -> Result<T::Data, Box<dyn std::error::Error>> {
56        let path = T::path(&a);
57
58        let uri = format!("/api/atlas/v1.0{}", &path);
59        let url = format!("https://cloud.mongodb.com{}", &uri);
60
61        let resp = reqwest::get(&url).await?;
62
63        let www_authenticate = resp.headers().get(WWW_AUTHENTICATE).unwrap().to_str()?;
64        let body = T::body(&a)?;
65        let context = AuthContext::new_with_method(
66            &self.username,
67            &self.password,
68            uri,
69            body.as_ref(),
70            T::method().into(),
71        );
72
73        let mut prompt = digest_auth::parse(www_authenticate).unwrap();
74        let answer = prompt.respond(&context).unwrap().to_string();
75        let client = reqwest::Client::new();
76        let req = client
77            .request(T::method(), url)
78            .header(AUTHORIZATION, answer);
79
80        let req = apply_data(req, body);
81
82        let resp = req.send().await?;
83
84        let result = T::get_data(resp).await?;
85
86        Ok(result)
87    }
88}
89
90#[derive(Debug, Serialize, Deserialize, thiserror::Error)]
91#[error("Error {error}: {reason}")]
92pub struct RequestError {
93    detail: String,
94    error: i32,
95    #[serde(rename = "errorCode")]
96    error_code: String,
97    parameters: Vec<String>,
98    reason: String,
99}
100
101#[derive(Debug)]
102pub struct Pagination {
103    items_per_page: i32,
104    page_number: i32,
105}
106
107impl Pagination {
108    pub fn new() -> Self {
109        Self {
110            ..Default::default()
111        }
112    }
113
114    pub fn items_per_page(self, items_per_page: i32) -> Self {
115        Self {
116            items_per_page,
117            ..self
118        }
119    }
120
121    pub fn page_number(self, page_number: i32) -> Self {
122        Self {
123            page_number,
124            ..self
125        }
126    }
127
128    pub fn to_query_params(&self) -> String {
129        format!(
130            "includeCount=true&itemsPerPage={}&pageNum={}",
131            self.items_per_page, self.page_number
132        )
133    }
134}
135
136impl Default for Pagination {
137    fn default() -> Self {
138        Self {
139            items_per_page: 100,
140            page_number: 1,
141        }
142    }
143}