tellme_client/
lib.rs

1#![feature(let_chains)]
2
3use std::error::Error;
4
5#[derive(Debug, Clone)]
6pub struct TellmeClient{
7    url     : url::Url,
8    login   : Option<String>,
9    password: Option<String>,
10}
11
12#[derive(serde::Serialize, serde::Deserialize)]
13struct Identifier{
14    identifier: String,
15}
16
17#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
18pub struct Service {
19    pub service_type         : String,
20    pub available            : bool,
21    pub healthcheck_endpoint : String,
22    pub is_accepted          : bool,
23    pub identifier           : String,
24    pub ip                   : url::Url,
25}
26
27impl TellmeClient {
28    pub fn new(url: url::Url, login: Option<String>, password: Option<String>) -> Self {
29        Self { url, login, password }
30    }
31
32    pub async fn register(&self,
33                          port: u16,
34                          healthcheck_endpoint: String,
35                          access_token: String,
36                          service_type: String
37                        ) -> Result<String, Box<dyn Error>> {
38        let registration_endpoint = self.url.join("/me")?;
39        let params   = [("healthcheck_endpoint", healthcheck_endpoint),
40                        ("access_token",         access_token),
41                        ("service_type",         service_type),
42                        ("port",                 port.to_string())];
43        let client   = reqwest::Client::new();
44        let response = client.post(registration_endpoint.to_string())
45                             .form(&params)
46                             .send()
47                             .await?
48                             .error_for_status()?;
49        let answer   = response.json::<Identifier>().await?;
50
51        Ok(answer.identifier)
52    }
53
54    pub async fn accept_service(&self, identifier: String) -> Result<(), Box<dyn Error>>{
55        if let Some(login) = &self.login &&
56           let Some(password) = &self.password{
57
58            let accept_endpoint = self.url.join("/accept_service")?;
59            let params = [("identifier", identifier),
60                          ("login",      login.clone()),
61                          ("password",   password.clone())];
62            let client = reqwest::Client::new();
63            let _ = client.post(accept_endpoint.to_string())
64                          .form(&params)
65                          .send()
66                          .await?
67                          .error_for_status()?;
68            return Ok(());
69        }
70        Err(Box::new(std::io::Error::new(
71                    std::io::ErrorKind::Other,
72                    "Login and password must be the set!")))
73    }
74
75    pub async fn disable_service(&self, identifier: String) -> Result<(), Box<dyn Error>>{
76        if let Some(login)    = &self.login    &&
77           let Some(password) = &self.password {
78            let disable_endpoint = self.url.join("/disable_service")?;
79            let params = [("identifier", identifier),
80                          ("login",      login.clone()),
81                          ("password",   password.clone())];
82            let client = reqwest::Client::new();
83            let _ = client.post(disable_endpoint.to_string())
84                          .form(&params)
85                          .send()
86                          .await?
87                          .error_for_status()?;
88            return Ok(());
89        }
90        Err(Box::new(std::io::Error::new(
91                    std::io::ErrorKind::Other,
92                    "Login and password must be the set!")))
93    }
94
95    pub async fn newtoken(&self) -> Result<String, Box<dyn Error>>{
96        #[derive(serde::Deserialize)]
97        struct Token {token: String}
98
99        if let Some(login)    = &self.login    &&
100           let Some(password) = &self.password {
101            let newtoken_endpoint = self.url.join("/newtoken")?;
102            let params   = [("login",    login.clone()),
103                            ("password", password.clone())];
104            let client   = reqwest::Client::new();
105            let token = client.post(newtoken_endpoint.to_string())
106                              .form(&params)
107                              .send()
108                              .await?
109                              .error_for_status()?.json::<Token>().await?;
110            return Ok(token.token);
111     }
112     Err(Box::new(std::io::Error::new(
113                 std::io::ErrorKind::Other,
114                 "Login and password must be the set!")))
115    }
116
117    pub async fn find(
118            &self,
119            service_type: Option<String>,
120            limit       : Option<usize>,
121            available   : Option<bool>
122        ) -> Result<Vec<Service>, Box<dyn Error>> {
123
124        let mut query_params = vec![];
125
126        if let Some(service_type) = service_type{
127            query_params.push(("service_type", service_type));
128        }
129        if let Some(limit) = limit{
130            query_params.push(("limit", limit.to_string()));
131        }
132        if let Some(available) = available{
133            query_params.push(("available", available.to_string()));
134        }
135
136        let find_endpoint = self.url.join("/find")?;
137        let client = reqwest::Client::new();
138        let response = client.get(find_endpoint.to_string()).query(&query_params).send().await?;
139        let services = response.json::<Vec<Service>>().await?;
140
141        Ok(services)
142    }
143
144    pub async fn subscribe(
145        &self,
146        identifier     : String,
147        on_registration: bool,
148        on_acceptance  : bool,
149        endpoint       : String
150        ) -> Result<(), Box<dyn Error>>{
151
152        if let Some(login)    = &self.login   &&
153           let Some(password) = &self.password {
154
155            let newtoken_endpoint = self.url.join("/subscribe")?;
156            let params   = [("login",           login.clone()),
157                            ("password",        password.clone()),
158                            ("identifier",      identifier),
159                            ("endpoint",        endpoint),
160                            ("on_registration", on_registration.to_string()),
161                            ("on_acceptance",   on_acceptance.to_string())];
162            let client   = reqwest::Client::new();
163            let _ = client.post(newtoken_endpoint.to_string())
164                          .form(&params)
165                          .send()
166                          .await?
167                          .error_for_status()?;
168            return Ok(());
169     }
170     Err(Box::new(std::io::Error::new(
171                 std::io::ErrorKind::Other,
172                 "Login and password must be the set!")))
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use crate::*;
179    #[test]
180    fn it_works() {
181        let my_port = 4567;
182
183        let client = TellmeClient::new(
184            url::Url::parse("http://localhost:8080").expect("Failed to parse service registry url"),
185            Some(String::from("login")),
186            Some(String::from("password"))
187        );
188
189        let access_token = client.newtoken()
190                                 .await
191                                 .expect("Failed to get access token!");
192
193        let identifier = client.register(my_port,
194                                         "/healthcheck_endpoint".to_owned(),
195                                         access_token,
196                                         "storage node".to_owned())
197                                         .await.expect("Failed to register self in service registry!");
198
199        // As we have login and password
200        // we can accept self in service registry
201        // Otherwise we need to wait someone to accept us
202
203        client.accept_service(identifier)
204              .await
205              .expect("Failed to accept self in service registry");
206
207        // We can also disable service if we need
208        client.disable_service(identifier)
209              .await
210              .expect("Failed to disable self in service registry");
211
212        // We can register endpoint for service registration and service acceptance
213        client.subscribe(identifier, true, false, "/hook/on_registration")
214              .await
215              .expect("Failed to subscribe to service registration");
216
217        client.subscribe(identifier, false, true, "/hook/on_acceptance")
218              .await
219              .expect("Failed to subscribe to service acceptance");
220
221        /*
222           Example of actix-web endpoint
223
224           #[actix-web::post("/hook/on_registration")]
225           async fn on_registration(service_data: actix_web::web::Form<tellme_client::Service) -> impl actix_web::Responder {
226              actix_web::HttpResponse::Accepted().finish()
227           }
228        */
229
230
231
232    }
233}