use crate::error::{ClientError, Error};
use crate::response::*;
use crate::selector::Selector;
use crate::util::{self, build_final_url, RuleKind, TargetState, ToBaseUrl};
use reqwest::header::{HeaderMap, HeaderValue, IntoHeaderName, CONTENT_TYPE};
use reqwest::Method as HttpMethod;
use serde::{de::DeserializeOwned, Serialize};
use std::borrow::Borrow;
use std::collections::HashMap;
use url::Url;
#[derive(Clone)]
pub struct InstantQueryBuilder {
client: Client,
params: Vec<(&'static str, String)>,
headers: Option<HeaderMap<HeaderValue>>,
}
impl InstantQueryBuilder {
pub fn at(mut self, time: i64) -> Self {
self.params.push(("time", time.to_string()));
self
}
pub fn timeout(mut self, timeout: i64) -> Self {
self.params.push(("timeout", format!("{}ms", timeout)));
self
}
pub fn stats(mut self) -> Self {
self.params.push(("stats", String::from("all")));
self
}
pub fn header<K: IntoHeaderName, T: Into<HeaderValue>>(mut self, name: K, value: T) -> Self {
self.headers
.get_or_insert_with(Default::default)
.append(name, value.into());
self
}
pub fn query(mut self, name: &'static str, value: impl ToString) -> Self {
self.params.push((name, value.to_string()));
self
}
pub async fn get(self) -> Result<PromqlResult, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn post(self) -> Result<PromqlResult, Error> {
let response = self.post_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
self.client
.send("api/v1/query", &self.params, HttpMethod::GET, self.headers)
.await
}
pub async fn post_raw(self) -> Result<reqwest::Response, Error> {
self.client
.send("api/v1/query", &self.params, HttpMethod::POST, self.headers)
.await
}
}
#[derive(Clone)]
pub struct RangeQueryBuilder {
client: Client,
params: Vec<(&'static str, String)>,
headers: Option<HeaderMap<HeaderValue>>,
}
impl RangeQueryBuilder {
pub fn timeout(mut self, timeout: i64) -> Self {
self.params.push(("timeout", format!("{}ms", timeout)));
self
}
pub fn stats(mut self) -> Self {
self.params.push(("stats", String::from("all")));
self
}
pub fn header<K: IntoHeaderName, T: Into<HeaderValue>>(mut self, name: K, value: T) -> Self {
self.headers
.get_or_insert_with(Default::default)
.append(name, value.into());
self
}
pub fn query(mut self, name: &'static str, value: impl ToString) -> Self {
self.params.push((name, value.to_string()));
self
}
pub async fn get(self) -> Result<PromqlResult, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn post(self) -> Result<PromqlResult, Error> {
let response = self.post_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
self.client
.send(
"api/v1/query_range",
&self.params,
HttpMethod::GET,
self.headers,
)
.await
}
pub async fn post_raw(self) -> Result<reqwest::Response, Error> {
self.client
.send(
"api/v1/query_range",
&self.params,
HttpMethod::POST,
self.headers,
)
.await
}
}
#[derive(Clone)]
pub struct RulesQueryBuilder {
client: Client,
kind: Option<RuleKind>,
names: Vec<String>,
groups: Vec<String>,
files: Vec<String>,
}
impl RulesQueryBuilder {
pub fn kind(mut self, kind: RuleKind) -> Self {
self.kind = Some(kind);
self
}
pub fn names<T>(mut self, names: T) -> Self
where
T: IntoIterator,
T::Item: std::fmt::Display,
{
self.names.extend(names.into_iter().map(|n| n.to_string()));
self
}
pub fn name(mut self, name: impl std::fmt::Display) -> Self {
self.names.push(name.to_string());
self
}
pub fn groups<T>(mut self, groups: T) -> Self
where
T: IntoIterator,
T::Item: std::fmt::Display,
{
self.groups
.extend(groups.into_iter().map(|g| g.to_string()));
self
}
pub fn group(mut self, group: impl std::fmt::Display) -> Self {
self.groups.push(group.to_string());
self
}
pub fn files<T>(mut self, files: T) -> Self
where
T: IntoIterator,
T::Item: std::fmt::Display,
{
self.files.extend(files.into_iter().map(|f| f.to_string()));
self
}
pub fn file(mut self, file: impl std::fmt::Display) -> Self {
self.files.push(file.to_string());
self
}
pub async fn get(self) -> Result<Vec<RuleGroup>, Error> {
let response = self.get_raw().await?;
Client::deserialize(response)
.await
.map(|r: RuleGroups| r.groups)
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
let mut params = vec![];
if let Some(k) = self.kind {
params.push(("type", k.to_query_param()))
}
for name in self.names {
params.push(("rule_name[]", name))
}
for group in self.groups {
params.push(("rule_group[]", group))
}
for file in self.files {
params.push(("file[]", file))
}
self.client
.send("api/v1/rules", ¶ms, HttpMethod::GET, None)
.await
}
}
#[derive(Clone)]
pub struct TargetMetadataQueryBuilder<'a> {
client: Client,
match_target: Option<Selector<'a>>,
metric: Option<String>,
limit: Option<i32>,
}
impl<'a> TargetMetadataQueryBuilder<'a> {
pub fn match_target(mut self, selector: &'a Selector<'a>) -> Self {
self.match_target = Some(selector.clone());
self
}
pub fn metric(mut self, metric: impl std::fmt::Display) -> Self {
self.metric = Some(metric.to_string());
self
}
pub fn limit(mut self, limit: i32) -> Self {
self.limit = Some(limit);
self
}
pub async fn get(self) -> Result<Vec<TargetMetadata>, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
let mut params = vec![];
if let Some(metric) = self.metric {
params.push(("metric", metric.to_string()))
}
if let Some(match_target) = self.match_target {
params.push(("match_target", match_target.to_string()))
}
if let Some(limit) = self.limit {
params.push(("limit", limit.to_string()))
}
self.client
.send("api/v1/targets/metadata", ¶ms, HttpMethod::GET, None)
.await
}
}
#[derive(Clone)]
pub struct MetricMetadataQueryBuilder {
client: Client,
metric: Option<String>,
limit: Option<i32>,
limit_per_metric: Option<i32>,
}
impl MetricMetadataQueryBuilder {
pub fn metric(mut self, metric: impl std::fmt::Display) -> Self {
self.metric = Some(metric.to_string());
self
}
pub fn limit(mut self, limit: i32) -> Self {
self.limit = Some(limit);
self
}
pub fn limit_per_metric(mut self, limit_per_metric: i32) -> Self {
self.limit_per_metric = Some(limit_per_metric);
self
}
pub async fn get(self) -> Result<HashMap<String, Vec<MetricMetadata>>, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
let mut params = vec![];
if let Some(metric) = self.metric {
params.push(("metric", metric.to_string()))
}
if let Some(limit) = self.limit {
params.push(("limit", limit.to_string()))
}
if let Some(limit_per_metric) = self.limit_per_metric {
params.push(("limit_per_metric", limit_per_metric.to_string()))
}
self.client
.send("api/v1/metadata", ¶ms, HttpMethod::GET, None)
.await
}
}
#[derive(Clone)]
pub struct SeriesQueryBuilder {
client: Client,
selectors: Vec<(&'static str, String)>,
start: Option<i64>,
end: Option<i64>,
}
impl SeriesQueryBuilder {
pub fn start(mut self, start: i64) -> Self {
self.start = Some(start);
self
}
pub fn end(mut self, end: i64) -> Self {
self.end = Some(end);
self
}
pub async fn get(self) -> Result<Vec<HashMap<String, String>>, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
let mut params = vec![];
if let Some(start) = self.start {
params.push(("start", start.to_string()));
}
if let Some(end) = self.end {
params.push(("end", end.to_string()));
}
params.extend(self.selectors);
self.client
.send("api/v1/series", ¶ms, HttpMethod::GET, None)
.await
}
}
#[derive(Clone)]
pub struct LabelNamesQueryBuilder {
client: Client,
selectors: Vec<(&'static str, String)>,
start: Option<i64>,
end: Option<i64>,
}
impl LabelNamesQueryBuilder {
pub fn selectors<'a, T>(mut self, selectors: T) -> Self
where
T: IntoIterator,
T::Item: Borrow<Selector<'a>>,
{
self.selectors.extend(
selectors
.into_iter()
.map(|s| ("match[]", s.borrow().to_string())),
);
self
}
pub fn start(mut self, start: i64) -> Self {
self.start = Some(start);
self
}
pub fn end(mut self, end: i64) -> Self {
self.end = Some(end);
self
}
pub async fn get(self) -> Result<Vec<String>, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
let mut params = vec![];
if let Some(start) = self.start {
params.push(("start", start.to_string()));
}
if let Some(end) = self.end {
params.push(("end", end.to_string()));
}
params.extend(self.selectors);
self.client
.send("api/v1/labels", ¶ms, HttpMethod::GET, None)
.await
}
}
#[derive(Clone)]
pub struct LabelValuesQueryBuilder {
client: Client,
label: String,
selectors: Vec<(&'static str, String)>,
start: Option<i64>,
end: Option<i64>,
}
impl LabelValuesQueryBuilder {
pub fn selectors<'a, T>(mut self, selectors: T) -> Self
where
T: IntoIterator,
T::Item: Borrow<Selector<'a>>,
{
self.selectors.extend(
selectors
.into_iter()
.map(|s| ("match[]", s.borrow().to_string())),
);
self
}
pub fn start(mut self, start: i64) -> Self {
self.start = Some(start);
self
}
pub fn end(mut self, end: i64) -> Self {
self.end = Some(end);
self
}
pub async fn get(self) -> Result<Vec<String>, Error> {
let response = self.get_raw().await?;
Client::deserialize(response).await
}
pub async fn get_raw(self) -> Result<reqwest::Response, Error> {
let mut params = vec![];
if let Some(start) = self.start {
params.push(("start", start.to_string()));
}
if let Some(end) = self.end {
params.push(("end", end.to_string()));
}
params.extend(self.selectors);
let path = format!("api/v1/label/{}/values", self.label);
self.client
.send(&path, ¶ms, HttpMethod::GET, None)
.await
}
}
#[derive(Clone)]
pub struct Client {
pub(crate) client: reqwest::Client,
pub(crate) base_url: Url,
}
impl Default for Client {
fn default() -> Self {
Client {
client: reqwest::Client::new(),
base_url: Url::parse("http://127.0.0.1:9090/").unwrap(),
}
}
}
impl std::str::FromStr for Client {
type Err = crate::error::Error;
fn from_str(url: &str) -> Result<Self, Self::Err> {
let client = Client {
base_url: url.to_base_url()?,
client: reqwest::Client::new(),
};
Ok(client)
}
}
impl std::convert::TryFrom<&str> for Client {
type Error = crate::error::Error;
fn try_from(url: &str) -> Result<Self, Self::Error> {
let client = Client {
base_url: url.to_base_url()?,
client: reqwest::Client::new(),
};
Ok(client)
}
}
impl std::convert::TryFrom<String> for Client {
type Error = crate::error::Error;
fn try_from(url: String) -> Result<Self, Self::Error> {
let client = Client {
base_url: url.to_base_url()?,
client: reqwest::Client::new(),
};
Ok(client)
}
}
impl Client {
pub fn inner(&self) -> &reqwest::Client {
&self.client
}
pub fn base_url(&self) -> &Url {
&self.base_url
}
pub fn from(client: reqwest::Client, url: &str) -> Result<Self, Error> {
let base_url = url.to_base_url()?;
Ok(Client { base_url, client })
}
async fn send<S: Serialize>(
&self,
path: &str,
params: &S,
method: HttpMethod,
headers: Option<HeaderMap<HeaderValue>>,
) -> Result<reqwest::Response, Error> {
let url = build_final_url(self.base_url.clone(), path);
let mut request = match method {
HttpMethod::GET => self.client.get(url).query(params),
HttpMethod::POST => self.client.post(url).form(params),
_ => unreachable!(),
};
if let Some(headers) = headers {
request = request.headers(headers);
}
let response = request.send().await.map_err(|source| {
Error::Client(ClientError {
message: "failed to send request to server",
source: Some(source),
})
})?;
Ok(response)
}
pub fn query(&self, query: impl std::fmt::Display) -> InstantQueryBuilder {
InstantQueryBuilder {
client: self.clone(),
params: vec![("query", query.to_string())],
headers: Default::default(),
}
}
pub fn query_range(
&self,
query: impl std::fmt::Display,
start: i64,
end: i64,
step: f64,
) -> RangeQueryBuilder {
RangeQueryBuilder {
client: self.clone(),
params: vec![
("query", query.to_string()),
("start", start.to_string()),
("end", end.to_string()),
("step", step.to_string()),
],
headers: Default::default(),
}
}
pub fn series<'a, T>(&self, selectors: T) -> Result<SeriesQueryBuilder, Error>
where
T: IntoIterator,
T::Item: Borrow<Selector<'a>>,
{
let selectors: Vec<(&str, String)> = selectors
.into_iter()
.map(|s| ("match[]", s.borrow().to_string()))
.collect();
if selectors.is_empty() {
Err(Error::EmptySeriesSelector)
} else {
Ok(SeriesQueryBuilder {
client: self.clone(),
selectors,
start: None,
end: None,
})
}
}
pub fn label_names(&self) -> LabelNamesQueryBuilder {
LabelNamesQueryBuilder {
client: self.clone(),
selectors: vec![],
start: None,
end: None,
}
}
pub fn label_values(&self, label: impl std::fmt::Display) -> LabelValuesQueryBuilder {
LabelValuesQueryBuilder {
client: self.clone(),
label: label.to_string(),
selectors: vec![],
start: None,
end: None,
}
}
pub async fn targets(&self, state: Option<TargetState>) -> Result<Targets, Error> {
let mut params = vec![];
if let Some(s) = &state {
params.push(("state", s.to_string()))
}
let response = self
.send("api/v1/targets", ¶ms, HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub fn rules(&self) -> RulesQueryBuilder {
RulesQueryBuilder {
client: self.clone(),
kind: None,
names: vec![],
groups: vec![],
files: vec![],
}
}
pub async fn alerts(&self) -> Result<Vec<Alert>, Error> {
let response = self
.send("api/v1/alerts", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response)
.await
.map(|r: Alerts| r.alerts)
}
pub async fn flags(&self) -> Result<HashMap<String, String>, Error> {
let response = self
.send("api/v1/status/flags", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub async fn build_information(&self) -> Result<BuildInformation, Error> {
let response = self
.send("api/v1/status/buildinfo", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub async fn runtime_information(&self) -> Result<RuntimeInformation, Error> {
let response = self
.send("api/v1/status/runtimeinfo", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub async fn tsdb_statistics(&self) -> Result<TsdbStatistics, Error> {
let response = self
.send("api/v1/status/tsdb", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub async fn wal_replay_statistics(&self) -> Result<WalReplayStatistics, Error> {
let response = self
.send("api/v1/status/walreplay", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub async fn alertmanagers(&self) -> Result<Alertmanagers, Error> {
let response = self
.send("api/v1/alertmanagers", &(), HttpMethod::GET, None)
.await?;
Client::deserialize(response).await
}
pub fn target_metadata<'a>(&self) -> TargetMetadataQueryBuilder<'a> {
TargetMetadataQueryBuilder {
client: self.clone(),
match_target: None,
metric: None,
limit: None,
}
}
pub fn metric_metadata(&self) -> MetricMetadataQueryBuilder {
MetricMetadataQueryBuilder {
client: self.clone(),
metric: None,
limit: None,
limit_per_metric: None,
}
}
pub async fn is_server_healthy(&self) -> Result<bool, Error> {
let url = build_final_url(self.base_url.clone(), "-/healthy");
self.client
.get(url)
.send()
.await
.map_err(|source| {
Error::Client(ClientError {
message: "failed to send request to health endpoint",
source: Some(source),
})
})?
.error_for_status()
.map_err(|source| {
Error::Client(ClientError {
message: "request to health endpoint returned an error",
source: Some(source),
})
})
.map(|_| true)
}
pub async fn is_server_ready(&self) -> Result<bool, Error> {
let url = build_final_url(self.base_url.clone(), "-/ready");
self.client
.get(url)
.send()
.await
.map_err(|source| {
Error::Client(ClientError {
message: "failed to send request to readiness endpoint",
source: Some(source),
})
})?
.error_for_status()
.map_err(|source| {
Error::Client(ClientError {
message: "request to readiness endpoint returned an error",
source: Some(source),
})
})
.map(|_| true)
}
async fn deserialize<D: DeserializeOwned>(response: reqwest::Response) -> Result<D, Error> {
let header = CONTENT_TYPE;
if !util::is_json(response.headers().get(header)) {
return Err(Error::Client(ClientError {
message: "failed to parse response from server due to invalid media type",
source: response.error_for_status().err(),
}));
}
let response = response.json::<ApiResponse<D>>().await.map_err(|source| {
Error::Client(ClientError {
message: "failed to parse JSON response from server",
source: Some(source),
})
})?;
match response {
ApiResponse::Success { data } => Ok(data),
ApiResponse::Error(e) => Err(Error::Prometheus(e)),
}
}
}