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(¶ms)
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(¶ms)
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(¶ms)
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(¶ms)
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(¶ms)
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 client.accept_service(identifier)
204 .await
205 .expect("Failed to accept self in service registry");
206
207 client.disable_service(identifier)
209 .await
210 .expect("Failed to disable self in service registry");
211
212 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 }
233}