1use reqwest::Client;
2use serde::de::DeserializeOwned;
3use serde::Deserialize;
4use std::collections::HashMap;
5
6#[derive(Debug, Deserialize)]
7pub struct DataPoint {
8 t: u64,
9 v: u32,
10}
11
12#[derive(Debug, Deserialize)]
13pub struct Error {
14 reason: String,
15}
16
17#[derive(Debug, Deserialize)]
18pub struct Status {
19 status: HashMap<String, String>
20}
21
22#[derive(Debug, Deserialize)]
23pub struct Incident {
24 t: i64,
25 v: IncidentDetails,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct IncidentDetails {
30 title: String,
31 body: String,
32 status: String,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct Incidents {
37 incidents: HashMap<String, Vec<Incident>>,
38}
39
40#[derive(Debug, Deserialize)]
41pub struct InventoryItem {
42 name: String,
43 region: String,
44 is_replica: bool,
45 cluster: String,
46}
47
48#[derive(Debug, Deserialize)]
49pub struct Inventory {
50 inventory: Vec<InventoryItem>,
51}
52
53#[derive(Debug, Deserialize)]
54pub struct MetricsGroup {
55 #[serde(default)]
56 latency: Option<HashMap<String, Vec<DataPoint>>>,
57 #[serde(default)]
58 indexing: Option<HashMap<String, Vec<DataPoint>>>,
59 #[serde(default)]
60 avg_build_time: Option<HashMap<String, Vec<DataPoint>>>,
61 #[serde(default)]
62 ssd_usage: Option<HashMap<String, Vec<DataPoint>>>,
63 #[serde(default)]
64 ram_search_usage: Option<HashMap<String, Vec<DataPoint>>>,
65 #[serde(default)]
66 ram_indexing_usage: Option<HashMap<String, Vec<DataPoint>>>,
67 #[serde(default)]
68 cpu_usage: Option<HashMap<String, Vec<DataPoint>>>,
69}
70
71#[derive(Debug, Deserialize)]
72pub struct Metrics {
73 metrics: MetricsGroup,
74}
75
76pub struct AlgoliaMonitoring {
77 api_key: Option<String>,
78 app_id: Option<String>,
79}
80
81impl AlgoliaMonitoring {
82 pub fn new(api_key: Option<String>, app_id: Option<String>) -> Self {
83 AlgoliaMonitoring { api_key, app_id }
84 }
85
86 pub async fn get_status(&self, servers: Option<Vec<String>>) -> Result<Status, Error> {
89 let servers = match servers.map(|s| s.join(",")) {
90 Some(s) => format!("/{}", s),
91 None => "".to_owned(),
92 };
93
94 let path = format!("status{}", servers);
95 self.fetch_data::<Status>(path.as_str()).await
96 }
97
98 pub async fn get_incidents(&self, servers: Option<Vec<String>>) -> Result<Incidents, Error> {
101 let servers = match servers.map(|s| s.join(",")) {
102 Some(s) => format!("/{}", s),
103 None => "".to_owned(),
104 };
105 let path = format!("incidents/{}", servers);
106 self.fetch_data::<Incidents>(path.as_str()).await
107 }
108
109 pub async fn get_inventory(&self) -> Result<Inventory, Error> {
111 self.fetch_data::<Inventory>("inventory").await
112 }
113
114 pub async fn get_latency(&self, servers: Vec<String>) -> Result<Metrics, Error> {
117 let servers = servers.join(",");
118 let path = format!("latency/{}", servers);
119 self.fetch_data::<Metrics>(path.as_str()).await
120 }
121
122 pub async fn get_reachability(&self, servers: Vec<String>) -> Result<Metrics, Error> {
125 let servers = servers.join(",");
126 let path = format!("reachability/{}/probes", servers);
127 self.fetch_data::<Metrics>(path.as_str()).await
128 }
129
130 pub async fn get_infrastructure_metrics(&self, metric: String, period: String) -> Result<Metrics, Error> {
145 let path = format!("infrastructure/{}/period/{}", metric, period);
146 self.fetch_data::<Metrics>(path.as_str()).await
147 }
148
149 fn get_http_client(&self) -> Result<Client, reqwest::Error> {
150 let mut headers = reqwest::header::HeaderMap::new();
151 if !self.api_key.is_some() && !self.app_id.is_some() {
152 headers.insert("X-Algolia-API-Key", self.api_key.as_ref().unwrap().parse().unwrap());
153 headers.insert("X-Algolia-Application-Id", self.app_id.as_ref().unwrap().parse().unwrap());
154 }
155 Client::builder().default_headers(headers).build()
156 }
157
158 fn get_endpoint(&self) -> String {
159 "https://status.algolia.com/1".to_owned()
160 }
161
162 async fn fetch_data<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
163 let client = self.get_http_client().map_err(|e| Error {
164 reason: e.to_string(),
165 })?;
166
167 let url = format!("{}/{}", self.get_endpoint(), path);
168 let response = client.get(&url).send().await.map_err(|e| Error {
169 reason: e.to_string(),
170 })?;
171
172 response.json::<T>().await.map_err(|e| Error {
173 reason: e.to_string(),
174 })
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 fn create_algolia_monitoring() -> AlgoliaMonitoring {
183 AlgoliaMonitoring::new(Some("your_api_key".to_string()), Some("your_app_id".to_string()))
184 }
185
186 #[test]
187 fn test_new() {
188 let algolia_monitoring = create_algolia_monitoring();
189 assert_eq!(algolia_monitoring.api_key, Some("your_api_key".to_string()));
190 assert_eq!(algolia_monitoring.app_id, Some("your_app_id".to_string()));
191 }
192}