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", &params, 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", &params, 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", &params, 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", &params, 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", &params, 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, &params, 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", &params, 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}