prometheus_http_query/client.rs
1use crate::error::{ClientError, Error};
2use crate::response::*;
3use crate::selector::Selector;
4use crate::util::{self, build_final_url, RuleKind, TargetState, ToBaseUrl};
5use reqwest::header::{HeaderMap, HeaderValue, IntoHeaderName, CONTENT_TYPE};
6use reqwest::Method as HttpMethod;
7use serde::{de::DeserializeOwned, Serialize};
8use std::borrow::Borrow;
9use std::collections::HashMap;
10use url::Url;
11
12/// Provides a builder to set some query parameters in the context
13/// of an instant query before sending it to Prometheus.
14#[derive(Clone)]
15pub struct InstantQueryBuilder {
16 client: Client,
17 params: Vec<(&'static str, String)>,
18 headers: Option<HeaderMap<HeaderValue>>,
19}
20
21impl InstantQueryBuilder {
22 /// Set the evaluation timestamp (Unix timestamp in seconds, e.g. 1659182624).
23 /// If this is not set the evaluation timestamp will default to the current Prometheus
24 /// server time.
25 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries)
26 pub fn at(mut self, time: i64) -> Self {
27 self.params.push(("time", time.to_string()));
28 self
29 }
30
31 /// Set the evaluation timeout (milliseconds, e.g. 1000).
32 /// If this is not set the timeout will default to the value of the "-query.timeout" flag of the Prometheus server.
33 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries)
34 pub fn timeout(mut self, timeout: i64) -> Self {
35 self.params.push(("timeout", format!("{}ms", timeout)));
36 self
37 }
38
39 /// Instruct Prometheus to compile query statistics as part of the API response.
40 pub fn stats(mut self) -> Self {
41 self.params.push(("stats", String::from("all")));
42 self
43 }
44
45 /// Include an additional header to the request.
46 pub fn header<K: IntoHeaderName, T: Into<HeaderValue>>(mut self, name: K, value: T) -> Self {
47 self.headers
48 .get_or_insert_with(Default::default)
49 .append(name, value.into());
50 self
51 }
52
53 /// Include an additional parameter to the request.
54 pub fn query(mut self, name: &'static str, value: impl ToString) -> Self {
55 self.params.push((name, value.to_string()));
56 self
57 }
58
59 /// Execute the instant query (using HTTP GET) and return the parsed API response.
60 pub async fn get(self) -> Result<PromqlResult, Error> {
61 let response = self.get_raw().await?;
62 Client::deserialize(response).await
63 }
64
65 /// Execute the instant query (using HTTP POST) and return the parsed API response.
66 /// Using a POST request is useful in the context of larger PromQL queries when
67 /// the size of the final URL may break Prometheus' or an intermediate proxies' URL
68 /// character limits.
69 pub async fn post(self) -> Result<PromqlResult, Error> {
70 let response = self.post_raw().await?;
71 Client::deserialize(response).await
72 }
73
74 /// Execute the instant query (using HTTP GET) and return the raw API response.
75 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
76 self.client
77 .send("api/v1/query", &self.params, HttpMethod::GET, self.headers)
78 .await
79 }
80
81 /// Execute the instant query (using HTTP POST) and return the raw API response.
82 /// Using a POST request is useful in the context of larger PromQL queries when
83 /// the size of the final URL may break Prometheus' or an intermediate proxies' URL
84 /// character limits.
85 pub async fn post_raw(self) -> Result<reqwest::Response, Error> {
86 self.client
87 .send("api/v1/query", &self.params, HttpMethod::POST, self.headers)
88 .await
89 }
90}
91
92/// Provides a builder to set some query parameters in the context
93/// of a range query before sending it to Prometheus.
94#[derive(Clone)]
95pub struct RangeQueryBuilder {
96 client: Client,
97 params: Vec<(&'static str, String)>,
98 headers: Option<HeaderMap<HeaderValue>>,
99}
100
101impl RangeQueryBuilder {
102 /// Set the evaluation timeout (milliseconds, e.g. 1000).
103 /// If this is not set the timeout will default to the value of the "-query.timeout" flag of the Prometheus server.
104 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
105 pub fn timeout(mut self, timeout: i64) -> Self {
106 self.params.push(("timeout", format!("{}ms", timeout)));
107 self
108 }
109
110 /// Instruct Prometheus to compile query statistics as part of the API response.
111 pub fn stats(mut self) -> Self {
112 self.params.push(("stats", String::from("all")));
113 self
114 }
115
116 /// Include an additional header to the request.
117 pub fn header<K: IntoHeaderName, T: Into<HeaderValue>>(mut self, name: K, value: T) -> Self {
118 self.headers
119 .get_or_insert_with(Default::default)
120 .append(name, value.into());
121 self
122 }
123
124 /// Include an additional parameter to the request.
125 pub fn query(mut self, name: &'static str, value: impl ToString) -> Self {
126 self.params.push((name, value.to_string()));
127 self
128 }
129
130 /// Execute the range query (using HTTP GET) and return the parsed API response.
131 pub async fn get(self) -> Result<PromqlResult, Error> {
132 let response = self.get_raw().await?;
133 Client::deserialize(response).await
134 }
135
136 /// Execute the instant query (using HTTP POST) and return the parsed API response.
137 /// Using a POST request is useful in the context of larger PromQL queries when
138 /// the size of the final URL may break Prometheus' or an intermediate proxies' URL
139 /// character limits.
140 pub async fn post(self) -> Result<PromqlResult, Error> {
141 let response = self.post_raw().await?;
142 Client::deserialize(response).await
143 }
144
145 /// Execute the range query (using HTTP GET) and return the raw API response.
146 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
147 self.client
148 .send(
149 "api/v1/query_range",
150 &self.params,
151 HttpMethod::GET,
152 self.headers,
153 )
154 .await
155 }
156
157 /// Execute the instant query (using HTTP POST) and return the raw API response.
158 /// Using a POST request is useful in the context of larger PromQL queries when
159 /// the size of the final URL may break Prometheus' or an intermediate proxies' URL
160 /// character limits.
161 pub async fn post_raw(self) -> Result<reqwest::Response, Error> {
162 self.client
163 .send(
164 "api/v1/query_range",
165 &self.params,
166 HttpMethod::POST,
167 self.headers,
168 )
169 .await
170 }
171}
172
173/// Provides methods to build a query to the rules endpoint and send it to Prometheus.
174#[derive(Clone)]
175pub struct RulesQueryBuilder {
176 client: Client,
177 kind: Option<RuleKind>,
178 names: Vec<String>,
179 groups: Vec<String>,
180 files: Vec<String>,
181}
182
183/// Note that Prometheus combines all filters that have been set in the final request
184/// and only returns rules that match all filters.<br>
185/// See the official documentation for a thorough explanation on the filters that can
186/// be set: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#rules).
187impl RulesQueryBuilder {
188 /// Set this to instruct Prometheus to only return a specific type of rule
189 /// (either recording or alerting rules) instead of both. Calling this repeatedly
190 /// will replace the current setting.
191 pub fn kind(mut self, kind: RuleKind) -> Self {
192 self.kind = Some(kind);
193 self
194 }
195
196 /// Pass rule names to instruct Prometheus to only return those rules whose
197 /// names match one of them. This method can be called repeatedly and merge
198 /// the names with those that have been set before.
199 pub fn names<T>(mut self, names: T) -> Self
200 where
201 T: IntoIterator,
202 T::Item: std::fmt::Display,
203 {
204 self.names.extend(names.into_iter().map(|n| n.to_string()));
205 self
206 }
207
208 /// Pass a rule name to instruct Prometheus to return rules that match this name.
209 /// This method can be called repeatedly to extend the set of rule names that
210 /// will be sent to Prometheus.
211 pub fn name(mut self, name: impl std::fmt::Display) -> Self {
212 self.names.push(name.to_string());
213 self
214 }
215
216 /// Pass group names to instruct Prometheus to only return those rules that are
217 /// part of one of these groups. This method can be called repeatedly and merge
218 /// the group names with those that have been set before.
219 pub fn groups<T>(mut self, groups: T) -> Self
220 where
221 T: IntoIterator,
222 T::Item: std::fmt::Display,
223 {
224 self.groups
225 .extend(groups.into_iter().map(|g| g.to_string()));
226 self
227 }
228
229 /// Pass a group name to instruct Prometheus to return rules that are part of this
230 /// group. This method can be called repeatedly to extend the set of group names
231 /// that will be sent to Prometheus.
232 pub fn group(mut self, group: impl std::fmt::Display) -> Self {
233 self.groups.push(group.to_string());
234 self
235 }
236
237 /// Pass file names to instruct Prometheus to only return those rules that are
238 /// defined in one of those files. This method can be called repeatedly and merge
239 /// the file names with those that have been set before.
240 pub fn files<T>(mut self, files: T) -> Self
241 where
242 T: IntoIterator,
243 T::Item: std::fmt::Display,
244 {
245 self.files.extend(files.into_iter().map(|f| f.to_string()));
246 self
247 }
248
249 /// Pass a file name to instruct Prometheus to return rules that are defined in
250 /// this file. This method can be called repeatedly to extend the set of file names
251 /// that will be sent to Prometheus.
252 pub fn file(mut self, file: impl std::fmt::Display) -> Self {
253 self.files.push(file.to_string());
254 self
255 }
256
257 /// Execute the rules query (using HTTP GET) and return the [`RuleGroup`]s sent
258 /// by Prometheus.
259 pub async fn get(self) -> Result<Vec<RuleGroup>, Error> {
260 let response = self.get_raw().await?;
261 Client::deserialize(response)
262 .await
263 .map(|r: RuleGroups| r.groups)
264 }
265
266 /// Execute the rules query (using HTTP GET) and return the raw response sent
267 /// by Prometheus.
268 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
269 let mut params = vec![];
270
271 if let Some(k) = self.kind {
272 params.push(("type", k.to_query_param()))
273 }
274
275 for name in self.names {
276 params.push(("rule_name[]", name))
277 }
278
279 for group in self.groups {
280 params.push(("rule_group[]", group))
281 }
282
283 for file in self.files {
284 params.push(("file[]", file))
285 }
286
287 self.client
288 .send("api/v1/rules", ¶ms, HttpMethod::GET, None)
289 .await
290 }
291}
292
293/// Provides methods to build a query to the target metadata endpoint and send it to Prometheus.
294#[derive(Clone)]
295pub struct TargetMetadataQueryBuilder<'a> {
296 client: Client,
297 match_target: Option<Selector<'a>>,
298 metric: Option<String>,
299 limit: Option<i32>,
300}
301
302/// Note that Prometheus combines all filters that have been set in the final request
303/// and only returns target metadata that matches all filters.<br>
304/// See the official documentation for a thorough explanation on the filters that can
305/// be set: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-target-metadata).
306impl<'a> TargetMetadataQueryBuilder<'a> {
307 /// Pass a label selector to instruct Prometheus to filter targets by their label
308 /// sets.
309 /// Calling this repeatedly will replace the current label selector.
310 pub fn match_target(mut self, selector: &'a Selector<'a>) -> Self {
311 self.match_target = Some(selector.clone());
312 self
313 }
314
315 /// Set this to only retrieve target metadata for this metric.
316 /// Calling this repeatedly will replace the current metric name.
317 pub fn metric(mut self, metric: impl std::fmt::Display) -> Self {
318 self.metric = Some(metric.to_string());
319 self
320 }
321
322 /// Limit the maximum number of targets to match.
323 /// Calling this repeatedly will replace the current limit.
324 pub fn limit(mut self, limit: i32) -> Self {
325 self.limit = Some(limit);
326 self
327 }
328
329 /// Execute the target metadata query (using HTTP GET) and return the collection of
330 /// [`TargetMetadata`] sent by Prometheus.
331 pub async fn get(self) -> Result<Vec<TargetMetadata>, Error> {
332 let response = self.get_raw().await?;
333 Client::deserialize(response).await
334 }
335
336 /// Execute the target metadata query (using HTTP GET) and return the raw response
337 /// sent by Prometheus.
338 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
339 let mut params = vec![];
340
341 if let Some(metric) = self.metric {
342 params.push(("metric", metric.to_string()))
343 }
344
345 if let Some(match_target) = self.match_target {
346 params.push(("match_target", match_target.to_string()))
347 }
348
349 if let Some(limit) = self.limit {
350 params.push(("limit", limit.to_string()))
351 }
352
353 self.client
354 .send("api/v1/targets/metadata", ¶ms, HttpMethod::GET, None)
355 .await
356 }
357}
358
359/// Provides methods to build a query to the metric metadata endpoint and send it to Prometheus.
360#[derive(Clone)]
361pub struct MetricMetadataQueryBuilder {
362 client: Client,
363 metric: Option<String>,
364 limit: Option<i32>,
365 limit_per_metric: Option<i32>,
366}
367
368/// Note that Prometheus combines all filters that have been set in the final request
369/// and only returns metric metadata that matches all filters.<br>
370/// See the official documentation for a thorough explanation on the filters that can
371/// be set: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata).
372impl MetricMetadataQueryBuilder {
373 /// Instruct Prometheus to filter metadata by this metric name.
374 /// Calling this repeatedly will replace the current setting.
375 pub fn metric(mut self, metric: impl std::fmt::Display) -> Self {
376 self.metric = Some(metric.to_string());
377 self
378 }
379
380 /// Limit the maximum number of metrics to return.
381 /// Calling this repeatedly will replace the current limit.
382 pub fn limit(mut self, limit: i32) -> Self {
383 self.limit = Some(limit);
384 self
385 }
386
387 /// Limit the maximum number of metadata to return per metric.
388 /// Calling this repeatedly will replace the current limit.
389 pub fn limit_per_metric(mut self, limit_per_metric: i32) -> Self {
390 self.limit_per_metric = Some(limit_per_metric);
391 self
392 }
393
394 /// Execute the metric metadata query (using HTTP GET) and return the collection of
395 /// [`MetricMetadata`] sent by Prometheus.
396 pub async fn get(self) -> Result<HashMap<String, Vec<MetricMetadata>>, Error> {
397 let response = self.get_raw().await?;
398 Client::deserialize(response).await
399 }
400
401 /// Execute the metric metadata query (using HTTP GET) and return the raw response
402 /// sent by Prometheus.
403 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
404 let mut params = vec![];
405
406 if let Some(metric) = self.metric {
407 params.push(("metric", metric.to_string()))
408 }
409
410 if let Some(limit) = self.limit {
411 params.push(("limit", limit.to_string()))
412 }
413
414 if let Some(limit_per_metric) = self.limit_per_metric {
415 params.push(("limit_per_metric", limit_per_metric.to_string()))
416 }
417
418 self.client
419 .send("api/v1/metadata", ¶ms, HttpMethod::GET, None)
420 .await
421 }
422}
423
424/// Provides methods to build a query to the series endpoint and send it to Prometheus.
425#[derive(Clone)]
426pub struct SeriesQueryBuilder {
427 client: Client,
428 selectors: Vec<(&'static str, String)>,
429 start: Option<i64>,
430 end: Option<i64>,
431}
432
433impl SeriesQueryBuilder {
434 /// Limit the amount of metadata returned by setting a start time
435 /// (UNIX timestamp in seconds).
436 /// Calling this repeatedly will replace the current setting.
437 pub fn start(mut self, start: i64) -> Self {
438 self.start = Some(start);
439 self
440 }
441
442 /// Limit the amount of metadata returned by setting an end time
443 /// (UNIX timestamp in seconds).
444 /// Calling this repeatedly will replace the current setting.
445 pub fn end(mut self, end: i64) -> Self {
446 self.end = Some(end);
447 self
448 }
449
450 /// Execute the series metadata query (using HTTP GET) and return a collection of
451 /// matching time series sent by Prometheus.
452 pub async fn get(self) -> Result<Vec<HashMap<String, String>>, Error> {
453 let response = self.get_raw().await?;
454 Client::deserialize(response).await
455 }
456
457 /// Execute the series metadata query (using HTTP GET) and return the raw response
458 /// sent by Prometheus.
459 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
460 let mut params = vec![];
461
462 if let Some(start) = self.start {
463 params.push(("start", start.to_string()));
464 }
465
466 if let Some(end) = self.end {
467 params.push(("end", end.to_string()));
468 }
469
470 params.extend(self.selectors);
471
472 self.client
473 .send("api/v1/series", ¶ms, HttpMethod::GET, None)
474 .await
475 }
476}
477
478/// Provides methods to build a query to retrieve label names from Prometheus.
479#[derive(Clone)]
480pub struct LabelNamesQueryBuilder {
481 client: Client,
482 selectors: Vec<(&'static str, String)>,
483 start: Option<i64>,
484 end: Option<i64>,
485}
486
487impl LabelNamesQueryBuilder {
488 /// Set series selectors to filter the time series from wich Prometheus
489 /// reads labels from.
490 /// This can be called multiple times to merge the series selectors with
491 /// those that have been set before.
492 pub fn selectors<'a, T>(mut self, selectors: T) -> Self
493 where
494 T: IntoIterator,
495 T::Item: Borrow<Selector<'a>>,
496 {
497 self.selectors.extend(
498 selectors
499 .into_iter()
500 .map(|s| ("match[]", s.borrow().to_string())),
501 );
502 self
503 }
504
505 /// Limit the amount of metadata returned by setting a start time
506 /// (UNIX timestamp in seconds).
507 /// Calling this repeatedly will replace the current setting.
508 pub fn start(mut self, start: i64) -> Self {
509 self.start = Some(start);
510 self
511 }
512
513 /// Limit the amount of metadata returned by setting an end time
514 /// (UNIX timestamp in seconds).
515 /// Calling this repeatedly will replace the current setting.
516 pub fn end(mut self, end: i64) -> Self {
517 self.end = Some(end);
518 self
519 }
520
521 /// Execute the query (using HTTP GET) and retrieve a collection of
522 /// label names.
523 pub async fn get(self) -> Result<Vec<String>, Error> {
524 let response = self.get_raw().await?;
525 Client::deserialize(response).await
526 }
527
528 /// Execute the query (using HTTP GET) and retrieve the raw response.
529 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
530 let mut params = vec![];
531
532 if let Some(start) = self.start {
533 params.push(("start", start.to_string()));
534 }
535
536 if let Some(end) = self.end {
537 params.push(("end", end.to_string()));
538 }
539
540 params.extend(self.selectors);
541
542 self.client
543 .send("api/v1/labels", ¶ms, HttpMethod::GET, None)
544 .await
545 }
546}
547
548/// Provides methods to build a query to retrieve label values for a specific
549/// label from Prometheus.
550#[derive(Clone)]
551pub struct LabelValuesQueryBuilder {
552 client: Client,
553 label: String,
554 selectors: Vec<(&'static str, String)>,
555 start: Option<i64>,
556 end: Option<i64>,
557}
558
559impl LabelValuesQueryBuilder {
560 /// Set series selectors to filter the time series from wich Prometheus
561 /// reads label values from.
562 /// This can be called multiple times to merge the series selectors with
563 /// those that have been set before.
564 pub fn selectors<'a, T>(mut self, selectors: T) -> Self
565 where
566 T: IntoIterator,
567 T::Item: Borrow<Selector<'a>>,
568 {
569 self.selectors.extend(
570 selectors
571 .into_iter()
572 .map(|s| ("match[]", s.borrow().to_string())),
573 );
574 self
575 }
576
577 /// Limit the amount of metadata returned by setting a start time
578 /// (UNIX timestamp in seconds).
579 /// Calling this repeatedly will replace the current setting.
580 pub fn start(mut self, start: i64) -> Self {
581 self.start = Some(start);
582 self
583 }
584
585 /// Limit the amount of metadata returned by setting an end time
586 /// (UNIX timestamp in seconds).
587 /// Calling this repeatedly will replace the current setting.
588 pub fn end(mut self, end: i64) -> Self {
589 self.end = Some(end);
590 self
591 }
592
593 /// Execute the query (using HTTP GET) and retrieve a collection of
594 /// label values for the given label name.
595 pub async fn get(self) -> Result<Vec<String>, Error> {
596 let response = self.get_raw().await?;
597 Client::deserialize(response).await
598 }
599
600 /// Execute the query (using HTTP GET) and retrieve a collection of
601 /// label values for the given label name.
602 pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
603 let mut params = vec![];
604
605 if let Some(start) = self.start {
606 params.push(("start", start.to_string()));
607 }
608
609 if let Some(end) = self.end {
610 params.push(("end", end.to_string()));
611 }
612
613 params.extend(self.selectors);
614
615 let path = format!("api/v1/label/{}/values", self.label);
616 self.client
617 .send(&path, ¶ms, HttpMethod::GET, None)
618 .await
619 }
620}
621
622/// A client used to execute queries. It uses a [`reqwest::Client`] internally
623/// that manages connections for us.
624#[derive(Clone)]
625pub struct Client {
626 pub(crate) client: reqwest::Client,
627 pub(crate) base_url: Url,
628}
629
630impl Default for Client {
631 /// Create a standard Client that sends requests to "http://127.0.0.1:9090/".
632 ///
633 /// ```rust
634 /// use prometheus_http_query::Client;
635 ///
636 /// let client = Client::default();
637 /// ```
638 fn default() -> Self {
639 Client {
640 client: reqwest::Client::new(),
641 base_url: Url::parse("http://127.0.0.1:9090/").unwrap(),
642 }
643 }
644}
645
646impl std::str::FromStr for Client {
647 type Err = crate::error::Error;
648
649 /// Create a Client from a custom base URL. Note that the API-specific
650 /// path segments (like `/api/v1/query`) are added automatically.
651 ///
652 /// ```rust
653 /// use prometheus_http_query::Client;
654 /// use std::str::FromStr;
655 ///
656 /// let client = Client::from_str("http://proxy.example.com/prometheus");
657 /// assert!(client.is_ok());
658 /// ```
659 fn from_str(url: &str) -> Result<Self, Self::Err> {
660 let client = Client {
661 base_url: url.to_base_url()?,
662 client: reqwest::Client::new(),
663 };
664 Ok(client)
665 }
666}
667
668impl std::convert::TryFrom<&str> for Client {
669 type Error = crate::error::Error;
670
671 /// Create a [`Client`] from a custom base URL. Note that the API-specific
672 /// path segments (like `/api/v1/query`) are added automatically.
673 ///
674 /// ```rust
675 /// use prometheus_http_query::Client;
676 /// use std::convert::TryFrom;
677 ///
678 /// let client = Client::try_from("http://proxy.example.com/prometheus");
679 /// assert!(client.is_ok());
680 /// ```
681 fn try_from(url: &str) -> Result<Self, Self::Error> {
682 let client = Client {
683 base_url: url.to_base_url()?,
684 client: reqwest::Client::new(),
685 };
686 Ok(client)
687 }
688}
689
690impl std::convert::TryFrom<String> for Client {
691 type Error = crate::error::Error;
692
693 /// Create a [`Client`] from a custom base URL. Note that the API-specific
694 /// path segments (like `/api/v1/query`) are added automatically.
695 ///
696 /// ```rust
697 /// use prometheus_http_query::Client;
698 /// use std::convert::TryFrom;
699 ///
700 /// let url = String::from("http://proxy.example.com/prometheus");
701 /// let client = Client::try_from(url);
702 /// assert!(client.is_ok());
703 /// ```
704 fn try_from(url: String) -> Result<Self, Self::Error> {
705 let client = Client {
706 base_url: url.to_base_url()?,
707 client: reqwest::Client::new(),
708 };
709 Ok(client)
710 }
711}
712
713impl Client {
714 /// Return a reference to the wrapped [`reqwest::Client`], i.e. to
715 /// use it for other requests unrelated to the Prometheus API.
716 ///
717 /// ```rust
718 /// use prometheus_http_query::{Client};
719 ///
720 /// #[tokio::main(flavor = "current_thread")]
721 /// async fn main() -> Result<(), anyhow::Error> {
722 /// let client = Client::default();
723 ///
724 /// // An amittedly bad example, but that is not the point.
725 /// let response = client
726 /// .inner()
727 /// .head("http://127.0.0.1:9090")
728 /// .send()
729 /// .await?;
730 ///
731 /// // Prometheus does not allow HEAD requests.
732 /// assert_eq!(response.status(), reqwest::StatusCode::METHOD_NOT_ALLOWED);
733 /// Ok(())
734 /// }
735 /// ```
736 pub fn inner(&self) -> &reqwest::Client {
737 &self.client
738 }
739
740 /// Return a reference to the base URL that is used in requests to
741 /// the Prometheus API.
742 ///
743 /// ```rust
744 /// use prometheus_http_query::Client;
745 /// use std::str::FromStr;
746 ///
747 /// let client = Client::default();
748 ///
749 /// assert_eq!(client.base_url().as_str(), "http://127.0.0.1:9090/");
750 ///
751 /// let client = Client::from_str("https://proxy.example.com:8443/prometheus").unwrap();
752 ///
753 /// assert_eq!(client.base_url().as_str(), "https://proxy.example.com:8443/prometheus");
754 /// ```
755 pub fn base_url(&self) -> &Url {
756 &self.base_url
757 }
758
759 /// Create a Client from a custom [`reqwest::Client`] and URL.
760 /// This way you can account for all extra parameters (e.g. x509 authentication)
761 /// that may be needed to connect to Prometheus or an intermediate proxy,
762 /// by building it into the [`reqwest::Client`].
763 ///
764 /// ```rust
765 /// use prometheus_http_query::Client;
766 ///
767 /// fn main() -> Result<(), anyhow::Error> {
768 /// let client = {
769 /// let c = reqwest::Client::builder()
770 /// .no_proxy()
771 /// .build()?;
772 /// Client::from(c, "https://prometheus.example.com")
773 /// };
774 ///
775 /// assert!(client.is_ok());
776 /// Ok(())
777 /// }
778 /// ```
779 pub fn from(client: reqwest::Client, url: &str) -> Result<Self, Error> {
780 let base_url = url.to_base_url()?;
781 Ok(Client { base_url, client })
782 }
783
784 /// Build and send the final HTTP request. Parse the result as JSON if the
785 /// `Content-Type` header indicates that the payload is JSON. Otherwise it is
786 /// assumed that an intermediate proxy sends a plain text error.
787 async fn send<S: Serialize>(
788 &self,
789 path: &str,
790 params: &S,
791 method: HttpMethod,
792 headers: Option<HeaderMap<HeaderValue>>,
793 ) -> Result<reqwest::Response, Error> {
794 let url = build_final_url(self.base_url.clone(), path);
795
796 let mut request = match method {
797 HttpMethod::GET => self.client.get(url).query(params),
798 HttpMethod::POST => self.client.post(url).form(params),
799 _ => unreachable!(),
800 };
801
802 if let Some(headers) = headers {
803 request = request.headers(headers);
804 }
805
806 let response = request.send().await.map_err(|source| {
807 Error::Client(ClientError {
808 message: "failed to send request to server",
809 source: Some(source),
810 })
811 })?;
812 Ok(response)
813 }
814
815 /// Create an [`InstantQueryBuilder`] from a PromQL query allowing you to set some query parameters
816 /// (e.g. evaluation timeout) before finally sending the instant query to the server.
817 ///
818 /// # Arguments
819 /// * `query` - PromQL query to exeute
820 ///
821 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries)
822 ///
823 /// ```rust
824 /// use prometheus_http_query::Client;
825 ///
826 /// #[tokio::main(flavor = "current_thread")]
827 /// async fn main() -> Result<(), anyhow::Error> {
828 /// let client = Client::default();
829 ///
830 /// let response = client.query("prometheus_http_request_total").get().await?;
831 ///
832 /// assert!(response.data().as_vector().is_some());
833 ///
834 /// // Or make a POST request.
835 /// let response = client.query("prometheus_http_request_total").post().await?;
836 ///
837 /// assert!(response.data().as_vector().is_some());
838 ///
839 /// Ok(())
840 /// }
841 /// ```
842 pub fn query(&self, query: impl std::fmt::Display) -> InstantQueryBuilder {
843 InstantQueryBuilder {
844 client: self.clone(),
845 params: vec![("query", query.to_string())],
846 headers: Default::default(),
847 }
848 }
849
850 /// Create a [`RangeQueryBuilder`] from a PromQL query allowing you to set some query parameters
851 /// (e.g. evaluation timeout) before finally sending the range query to the server.
852 ///
853 /// # Arguments
854 /// * `query` - PromQL query to exeute
855 /// * `start` - Start timestamp as Unix timestamp (seconds)
856 /// * `end` - End timestamp as Unix timestamp (seconds)
857 /// * `step` - Query resolution step width as float number of seconds
858 ///
859 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries)
860 ///
861 /// ```rust
862 /// use prometheus_http_query::Client;
863 ///
864 /// #[tokio::main(flavor = "current_thread")]
865 /// async fn main() -> Result<(), anyhow::Error> {
866 /// let client = Client::default();
867 ///
868 /// let q = "prometheus_http_requests_total";
869 ///
870 /// let response = client.query_range(q, 1648373100, 1648373300, 10.0).get().await?;
871 ///
872 /// assert!(response.data().as_matrix().is_some());
873 ///
874 /// // Or make a POST request.
875 /// let response = client.query_range(q, 1648373100, 1648373300, 10.0).post().await?;
876 ///
877 /// assert!(response.data().as_matrix().is_some());
878 ///
879 /// Ok(())
880 /// }
881 /// ```
882 pub fn query_range(
883 &self,
884 query: impl std::fmt::Display,
885 start: i64,
886 end: i64,
887 step: f64,
888 ) -> RangeQueryBuilder {
889 RangeQueryBuilder {
890 client: self.clone(),
891 params: vec![
892 ("query", query.to_string()),
893 ("start", start.to_string()),
894 ("end", end.to_string()),
895 ("step", step.to_string()),
896 ],
897 headers: Default::default(),
898 }
899 }
900
901 /// Create a [`SeriesQueryBuilder`] to apply filters to a series metadata
902 /// query before sending it to Prometheus.
903 ///
904 /// # Arguments
905 /// * `selectors` - Iterable container of [`Selector`]s that tells Prometheus which series to return. Must not be empty!
906 ///
907 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers)
908 ///
909 /// ```rust
910 /// use prometheus_http_query::{Client, Selector};
911 ///
912 /// #[tokio::main(flavor = "current_thread")]
913 /// async fn main() -> Result<(), anyhow::Error> {
914 /// let client = Client::default();
915 ///
916 /// let s1 = Selector::new()
917 /// .eq("handler", "/api/v1/query");
918 ///
919 /// let s2 = Selector::new()
920 /// .eq("job", "node")
921 /// .regex_eq("mode", ".+");
922 ///
923 /// let response = client.series(&[s1, s2])?.get().await;
924 ///
925 /// assert!(response.is_ok());
926 ///
927 /// Ok(())
928 /// }
929 /// ```
930 pub fn series<'a, T>(&self, selectors: T) -> Result<SeriesQueryBuilder, Error>
931 where
932 T: IntoIterator,
933 T::Item: Borrow<Selector<'a>>,
934 {
935 let selectors: Vec<(&str, String)> = selectors
936 .into_iter()
937 .map(|s| ("match[]", s.borrow().to_string()))
938 .collect();
939
940 if selectors.is_empty() {
941 Err(Error::EmptySeriesSelector)
942 } else {
943 Ok(SeriesQueryBuilder {
944 client: self.clone(),
945 selectors,
946 start: None,
947 end: None,
948 })
949 }
950 }
951
952 /// Create a [`LabelNamesQueryBuilder`] to apply filters to a query for the label
953 /// names endpoint before sending it to Prometheus.
954 ///
955 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names)
956 ///
957 /// ```rust
958 /// use prometheus_http_query::{Client, Selector};
959 ///
960 /// #[tokio::main(flavor = "current_thread")]
961 /// async fn main() -> Result<(), anyhow::Error> {
962 /// let client = Client::default();
963 ///
964 /// // To retrieve a list of all labels:
965 /// let response = client.label_names().get().await;
966 ///
967 /// assert!(response.is_ok());
968 ///
969 /// // Use a selector to retrieve a list of labels that appear in specific time series:
970 /// let s1 = Selector::new()
971 /// .eq("handler", "/api/v1/query");
972 ///
973 /// let s2 = Selector::new()
974 /// .eq("job", "node")
975 /// .regex_eq("mode", ".+");
976 ///
977 /// let response = client.label_names().selectors(&[s1, s2]).get().await;
978 ///
979 /// assert!(response.is_ok());
980 ///
981 /// Ok(())
982 /// }
983 /// ```
984 pub fn label_names(&self) -> LabelNamesQueryBuilder {
985 LabelNamesQueryBuilder {
986 client: self.clone(),
987 selectors: vec![],
988 start: None,
989 end: None,
990 }
991 }
992
993 /// Create a [`LabelValuesQueryBuilder`] to apply filters to a query for the label
994 /// values endpoint before sending it to Prometheus.
995 ///
996 /// # Arguments
997 /// * `label` - Name of the label to return all occuring label values for.
998 ///
999 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values)
1000 ///
1001 /// ```rust
1002 /// use prometheus_http_query::{Client, Selector};
1003 ///
1004 /// #[tokio::main(flavor = "current_thread")]
1005 /// async fn main() -> Result<(), anyhow::Error> {
1006 /// let client = Client::default();
1007 ///
1008 /// // To retrieve a list of all label values for a specific label name:
1009 /// let response = client.label_values("job").get().await;
1010 ///
1011 /// assert!(response.is_ok());
1012 ///
1013 /// // To retrieve a list of label values of labels that appear in specific time series:
1014 /// let s1 = Selector::new()
1015 /// .regex_eq("instance", ".+");
1016 ///
1017 /// let response = client.label_values("job").selectors(&[s1]).get().await;
1018 ///
1019 /// assert!(response.is_ok());
1020 ///
1021 /// Ok(())
1022 /// }
1023 /// ```
1024 pub fn label_values(&self, label: impl std::fmt::Display) -> LabelValuesQueryBuilder {
1025 LabelValuesQueryBuilder {
1026 client: self.clone(),
1027 label: label.to_string(),
1028 selectors: vec![],
1029 start: None,
1030 end: None,
1031 }
1032 }
1033
1034 /// Query the current state of target discovery.
1035 ///
1036 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#targets)
1037 ///
1038 /// ```rust
1039 /// use prometheus_http_query::{Client, TargetState};
1040 ///
1041 /// #[tokio::main(flavor = "current_thread")]
1042 /// async fn main() -> Result<(), anyhow::Error> {
1043 /// let client = Client::default();
1044 ///
1045 /// let response = client.targets(None).await;
1046 ///
1047 /// assert!(response.is_ok());
1048 ///
1049 /// // Filter targets by type:
1050 /// let response = client.targets(Some(TargetState::Active)).await;
1051 ///
1052 /// assert!(response.is_ok());
1053 ///
1054 /// Ok(())
1055 /// }
1056 /// ```
1057 pub async fn targets(&self, state: Option<TargetState>) -> Result<Targets, Error> {
1058 let mut params = vec![];
1059
1060 if let Some(s) = &state {
1061 params.push(("state", s.to_string()))
1062 }
1063
1064 let response = self
1065 .send("api/v1/targets", ¶ms, HttpMethod::GET, None)
1066 .await?;
1067 Client::deserialize(response).await
1068 }
1069
1070 /// Create a [`RulesQueryBuilder`] to apply filters to the rules query before
1071 /// sending it to Prometheus.
1072 ///
1073 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#rules)
1074 ///
1075 /// ```rust
1076 /// use prometheus_http_query::{Client, RuleKind};
1077 ///
1078 /// #[tokio::main(flavor = "current_thread")]
1079 /// async fn main() -> Result<(), anyhow::Error> {
1080 /// let client = Client::default();
1081 ///
1082 /// let response = client.rules().get().await;
1083 ///
1084 /// assert!(response.is_ok());
1085 ///
1086 /// // Filter rules by type:
1087 /// let response = client.rules().kind(RuleKind::Alerting).get().await;
1088 ///
1089 /// assert!(response.is_ok());
1090 ///
1091 /// Ok(())
1092 /// }
1093 /// ```
1094 pub fn rules(&self) -> RulesQueryBuilder {
1095 RulesQueryBuilder {
1096 client: self.clone(),
1097 kind: None,
1098 names: vec![],
1099 groups: vec![],
1100 files: vec![],
1101 }
1102 }
1103
1104 /// Retrieve a list of active alerts.
1105 ///
1106 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#alerts)
1107 ///
1108 /// ```rust
1109 /// use prometheus_http_query::Client;
1110 ///
1111 /// #[tokio::main(flavor = "current_thread")]
1112 /// async fn main() -> Result<(), anyhow::Error> {
1113 /// let client = Client::default();
1114 ///
1115 /// let response = client.alerts().await;
1116 ///
1117 /// assert!(response.is_ok());
1118 ///
1119 /// Ok(())
1120 /// }
1121 /// ```
1122 pub async fn alerts(&self) -> Result<Vec<Alert>, Error> {
1123 let response = self
1124 .send("api/v1/alerts", &(), HttpMethod::GET, None)
1125 .await?;
1126 Client::deserialize(response)
1127 .await
1128 .map(|r: Alerts| r.alerts)
1129 }
1130
1131 /// Retrieve a list of flags that Prometheus was configured with.
1132 ///
1133 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#flags)
1134 ///
1135 /// ```rust
1136 /// use prometheus_http_query::Client;
1137 ///
1138 /// #[tokio::main(flavor = "current_thread")]
1139 /// async fn main() -> Result<(), anyhow::Error> {
1140 /// let client = Client::default();
1141 ///
1142 /// let response = client.flags().await;
1143 ///
1144 /// assert!(response.is_ok());
1145 ///
1146 /// Ok(())
1147 /// }
1148 /// ```
1149 pub async fn flags(&self) -> Result<HashMap<String, String>, Error> {
1150 let response = self
1151 .send("api/v1/status/flags", &(), HttpMethod::GET, None)
1152 .await?;
1153 Client::deserialize(response).await
1154 }
1155
1156 /// Retrieve Prometheus server build information.
1157 ///
1158 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#build-information)
1159 ///
1160 /// ```rust
1161 /// use prometheus_http_query::Client;
1162 ///
1163 /// #[tokio::main(flavor = "current_thread")]
1164 /// async fn main() -> Result<(), anyhow::Error> {
1165 /// let client = Client::default();
1166 ///
1167 /// let response = client.build_information().await;
1168 ///
1169 /// assert!(response.is_ok());
1170 ///
1171 /// Ok(())
1172 /// }
1173 /// ```
1174 pub async fn build_information(&self) -> Result<BuildInformation, Error> {
1175 let response = self
1176 .send("api/v1/status/buildinfo", &(), HttpMethod::GET, None)
1177 .await?;
1178 Client::deserialize(response).await
1179 }
1180
1181 /// Retrieve Prometheus server runtime information.
1182 ///
1183 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#runtime-information)
1184 ///
1185 /// ```rust
1186 /// use prometheus_http_query::Client;
1187 ///
1188 /// #[tokio::main(flavor = "current_thread")]
1189 /// async fn main() -> Result<(), anyhow::Error> {
1190 /// let client = Client::default();
1191 ///
1192 /// let response = client.runtime_information().await;
1193 ///
1194 /// assert!(response.is_ok());
1195 ///
1196 /// Ok(())
1197 /// }
1198 /// ```
1199 pub async fn runtime_information(&self) -> Result<RuntimeInformation, Error> {
1200 let response = self
1201 .send("api/v1/status/runtimeinfo", &(), HttpMethod::GET, None)
1202 .await?;
1203 Client::deserialize(response).await
1204 }
1205
1206 /// Retrieve Prometheus TSDB statistics.
1207 ///
1208 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#tsdb-stats)
1209 ///
1210 /// ```rust
1211 /// use prometheus_http_query::Client;
1212 ///
1213 /// #[tokio::main(flavor = "current_thread")]
1214 /// async fn main() -> Result<(), anyhow::Error> {
1215 /// let client = Client::default();
1216 ///
1217 /// let response = client.tsdb_statistics().await;
1218 ///
1219 /// assert!(response.is_ok());
1220 ///
1221 /// Ok(())
1222 /// }
1223 /// ```
1224 pub async fn tsdb_statistics(&self) -> Result<TsdbStatistics, Error> {
1225 let response = self
1226 .send("api/v1/status/tsdb", &(), HttpMethod::GET, None)
1227 .await?;
1228 Client::deserialize(response).await
1229 }
1230
1231 /// Retrieve WAL replay statistics.
1232 ///
1233 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#wal-replay-stats)
1234 ///
1235 /// ```rust
1236 /// use prometheus_http_query::Client;
1237 ///
1238 /// #[tokio::main(flavor = "current_thread")]
1239 /// async fn main() -> Result<(), anyhow::Error> {
1240 /// let client = Client::default();
1241 ///
1242 /// let response = client.wal_replay_statistics().await;
1243 ///
1244 /// assert!(response.is_ok());
1245 ///
1246 /// Ok(())
1247 /// }
1248 /// ```
1249 pub async fn wal_replay_statistics(&self) -> Result<WalReplayStatistics, Error> {
1250 let response = self
1251 .send("api/v1/status/walreplay", &(), HttpMethod::GET, None)
1252 .await?;
1253 Client::deserialize(response).await
1254 }
1255
1256 /// Query the current state of alertmanager discovery.
1257 ///
1258 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#alertmanagers)
1259 ///
1260 /// ```rust
1261 /// use prometheus_http_query::Client;
1262 ///
1263 /// #[tokio::main(flavor = "current_thread")]
1264 /// async fn main() -> Result<(), anyhow::Error> {
1265 /// let client = Client::default();
1266 ///
1267 /// let response = client.alertmanagers().await;
1268 ///
1269 /// assert!(response.is_ok());
1270 ///
1271 /// Ok(())
1272 /// }
1273 /// ```
1274 pub async fn alertmanagers(&self) -> Result<Alertmanagers, Error> {
1275 let response = self
1276 .send("api/v1/alertmanagers", &(), HttpMethod::GET, None)
1277 .await?;
1278 Client::deserialize(response).await
1279 }
1280
1281 /// Create a [`TargetMetadataQueryBuilder`] to apply filters to a target metadata
1282 /// query before sending it to Prometheus.
1283 ///
1284 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-target-metadata)
1285 ///
1286 /// ```rust
1287 /// use prometheus_http_query::{Client, Selector};
1288 ///
1289 /// #[tokio::main(flavor = "current_thread")]
1290 /// async fn main() -> Result<(), anyhow::Error> {
1291 /// let client = Client::default();
1292 ///
1293 /// // Retrieve metadata for a specific metric from all targets.
1294 /// let response = client.target_metadata().metric("go_goroutines").get().await;
1295 ///
1296 /// assert!(response.is_ok());
1297 ///
1298 /// // Retrieve metric metadata from specific targets.
1299 /// let s = Selector::new().eq("job", "prometheus");
1300 ///
1301 /// let response = client.target_metadata().match_target(&s).get().await;
1302 ///
1303 /// assert!(response.is_ok());
1304 ///
1305 /// // Retrieve metadata for a specific metric from targets that match a specific label set.
1306 /// let s = Selector::new().eq("job", "node");
1307 ///
1308 /// let response = client.target_metadata()
1309 /// .metric("node_cpu_seconds_total")
1310 /// .match_target(&s)
1311 /// .get()
1312 /// .await;
1313 ///
1314 /// assert!(response.is_ok());
1315 ///
1316 /// Ok(())
1317 /// }
1318 /// ```
1319 pub fn target_metadata<'a>(&self) -> TargetMetadataQueryBuilder<'a> {
1320 TargetMetadataQueryBuilder {
1321 client: self.clone(),
1322 match_target: None,
1323 metric: None,
1324 limit: None,
1325 }
1326 }
1327
1328 /// Create a [`MetricMetadataQueryBuilder`] to apply filters to a metric metadata
1329 /// query before sending it to Prometheus.
1330 ///
1331 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata)
1332 ///
1333 /// ```rust
1334 /// use prometheus_http_query::Client;
1335 ///
1336 /// #[tokio::main(flavor = "current_thread")]
1337 /// async fn main() -> Result<(), anyhow::Error> {
1338 /// let client = Client::default();
1339 ///
1340 /// // Retrieve metadata for a all metrics.
1341 /// let response = client.metric_metadata().get().await;
1342 ///
1343 /// assert!(response.is_ok());
1344 ///
1345 /// // Limit the number of returned metrics.
1346 /// let response = client.metric_metadata().limit(100).get().await;
1347 ///
1348 /// assert!(response.is_ok());
1349 ///
1350 /// // Retrieve metadata for a specific metric but with a per-metric
1351 /// // metadata limit.
1352 /// let response = client.metric_metadata()
1353 /// .metric("go_goroutines")
1354 /// .limit_per_metric(5)
1355 /// .get()
1356 /// .await;
1357 ///
1358 /// assert!(response.is_ok());
1359 ///
1360 /// Ok(())
1361 /// }
1362 /// ```
1363 pub fn metric_metadata(&self) -> MetricMetadataQueryBuilder {
1364 MetricMetadataQueryBuilder {
1365 client: self.clone(),
1366 metric: None,
1367 limit: None,
1368 limit_per_metric: None,
1369 }
1370 }
1371
1372 /// Check Prometheus server health.
1373 ///
1374 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/management_api/#health-check)
1375 ///
1376 /// ```rust
1377 /// use prometheus_http_query::Client;
1378 ///
1379 /// #[tokio::main(flavor = "current_thread")]
1380 /// async fn main() -> Result<(), anyhow::Error> {
1381 /// let client = Client::default();
1382 /// assert!(client.is_server_healthy().await?);
1383 /// Ok(())
1384 /// }
1385 /// ```
1386 pub async fn is_server_healthy(&self) -> Result<bool, Error> {
1387 let url = build_final_url(self.base_url.clone(), "-/healthy");
1388 self.client
1389 .get(url)
1390 .send()
1391 .await
1392 .map_err(|source| {
1393 Error::Client(ClientError {
1394 message: "failed to send request to health endpoint",
1395 source: Some(source),
1396 })
1397 })?
1398 .error_for_status()
1399 .map_err(|source| {
1400 Error::Client(ClientError {
1401 message: "request to health endpoint returned an error",
1402 source: Some(source),
1403 })
1404 })
1405 .map(|_| true)
1406 }
1407
1408 /// Check Prometheus server readiness.
1409 ///
1410 /// See also: [Prometheus API documentation](https://prometheus.io/docs/prometheus/latest/management_api/#readiness-check)
1411 ///
1412 /// ```rust
1413 /// use prometheus_http_query::Client;
1414 ///
1415 /// #[tokio::main(flavor = "current_thread")]
1416 /// async fn main() -> Result<(), anyhow::Error> {
1417 /// let client = Client::default();
1418 /// assert!(client.is_server_ready().await?);
1419 /// Ok(())
1420 /// }
1421 /// ```
1422 pub async fn is_server_ready(&self) -> Result<bool, Error> {
1423 let url = build_final_url(self.base_url.clone(), "-/ready");
1424 self.client
1425 .get(url)
1426 .send()
1427 .await
1428 .map_err(|source| {
1429 Error::Client(ClientError {
1430 message: "failed to send request to readiness endpoint",
1431 source: Some(source),
1432 })
1433 })?
1434 .error_for_status()
1435 .map_err(|source| {
1436 Error::Client(ClientError {
1437 message: "request to readiness endpoint returned an error",
1438 source: Some(source),
1439 })
1440 })
1441 .map(|_| true)
1442 }
1443
1444 // Deserialize the raw reqwest response returned from the Prometheus server into a type `D` that implements serde's `Deserialize` trait.
1445 //
1446 // Internally, the response is deserialized into the [`ApiResponse`] type first.
1447 // On success, the data is returned as is. On failure, the error is mapped to the appropriate [`Error`] type.
1448 async fn deserialize<D: DeserializeOwned>(response: reqwest::Response) -> Result<D, Error> {
1449 let header = CONTENT_TYPE;
1450 if !util::is_json(response.headers().get(header)) {
1451 return Err(Error::Client(ClientError {
1452 message: "failed to parse response from server due to invalid media type",
1453 source: response.error_for_status().err(),
1454 }));
1455 }
1456 let response = response.json::<ApiResponse<D>>().await.map_err(|source| {
1457 Error::Client(ClientError {
1458 message: "failed to parse JSON response from server",
1459 source: Some(source),
1460 })
1461 })?;
1462 match response {
1463 ApiResponse::Success { data } => Ok(data),
1464 ApiResponse::Error(e) => Err(Error::Prometheus(e)),
1465 }
1466 }
1467}