Skip to content
Snippets Groups Projects
Commit 624874cb authored by Maaz Ahmed's avatar Maaz Ahmed
Browse files

Merge branch '6-replace-queryresult-and-error-type-with-internal-types' into 'main'

Resolve "refactor: Replace QueryResult and Error type with internal types"

Closes #6

See merge request subcom/mquery!4
parents 1c76c52e 5dc54938
No related branches found
No related tags found
1 merge request!4Resolve "refactor: Replace QueryResult and Error type with internal types"
...@@ -443,6 +443,7 @@ name = "mquery" ...@@ -443,6 +443,7 @@ name = "mquery"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"dotenv", "dotenv",
"enum-as-inner",
"prometheus-http-query", "prometheus-http-query",
"reqwest", "reqwest",
"tokio", "tokio",
......
...@@ -6,6 +6,7 @@ edition = "2021" ...@@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
enum-as-inner = "0.6.0"
prometheus-http-query = "0.8.0" prometheus-http-query = "0.8.0"
reqwest = "0.11.22" reqwest = "0.11.22"
url = "2.5.0" url = "2.5.0"
......
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
pub mod query; pub mod query;
pub mod result;
use prometheus_http_query::{response::PromqlResult, Client, Error}; use prometheus_http_query::Client;
use query::IntoQuery; use query::IntoQuery;
use reqwest::header::HeaderValue; use reqwest::header::HeaderValue;
use result::{IntoQryResult, QryResult};
use url::Url; use url::Url;
/// The primary way to send queries and get deserialized responses from the metrics server /// The primary way to send queries and get deserialized responses from the metrics server
...@@ -65,7 +67,7 @@ impl QueryManager { ...@@ -65,7 +67,7 @@ impl QueryManager {
/// The trait is implemented for `&str`, `String`, `&String`, and __mquery's__ /// The trait is implemented for `&str`, `String`, `&String`, and __mquery's__
/// internal types such as [`query::Metric`], [`query::Scalar`] and others that result /// internal types such as [`query::Metric`], [`query::Scalar`] and others that result
/// from the query builder API. /// from the query builder API.
pub async fn query<Q: IntoQuery>(&self, query: Q) -> Result<PromqlResult, Error> { pub async fn query<Q: IntoQuery>(&self, query: Q) -> QryResult {
let mut builder = self.client.query(query.into_query().as_ref()); let mut builder = self.client.query(query.into_query().as_ref());
if let Some((name, val)) = self.auth.get_header() { if let Some((name, val)) = self.auth.get_header() {
builder = builder.header(name, val); builder = builder.header(name, val);
...@@ -74,8 +76,8 @@ impl QueryManager { ...@@ -74,8 +76,8 @@ impl QueryManager {
builder = builder.timeout(*timeout); builder = builder.timeout(*timeout);
} }
match self.method { match self.method {
Method::Get => builder.get().await, Method::Get => builder.get().await.into_qry_result(),
Method::Post => builder.post().await, Method::Post => builder.post().await.into_qry_result(),
} }
} }
...@@ -91,7 +93,7 @@ impl QueryManager { ...@@ -91,7 +93,7 @@ impl QueryManager {
start: i64, start: i64,
end: i64, end: i64,
step: f64, step: f64,
) -> Result<PromqlResult, Error> { ) -> QryResult {
let mut builder = self let mut builder = self
.client .client
.query_range(query.into_query().as_ref(), start, end, step); .query_range(query.into_query().as_ref(), start, end, step);
...@@ -102,8 +104,8 @@ impl QueryManager { ...@@ -102,8 +104,8 @@ impl QueryManager {
builder = builder.timeout(*timeout); builder = builder.timeout(*timeout);
} }
match self.method { match self.method {
Method::Get => builder.get().await, Method::Get => builder.get().await.into_qry_result(),
Method::Post => builder.post().await, Method::Post => builder.post().await.into_qry_result(),
} }
} }
} }
......
//! `Data` and the `Error` types which are essentially clones of the types
//! with the same name from the `prometheus-http-query` crate with slight modifications
//! to suit the needs of `mqeury`.
//!
//! Another reason why this crate maintains its own types which are the same (almost) as the ones
//! provided by the other crate that is used internally, is to ensure that the public
//! API remains stable even if the internal crate API changes.
use enum_as_inner::EnumAsInner;
use prometheus_http_query::{error, response};
use std::fmt::{self, Display};
pub type QryResult = std::result::Result<Data, Error>;
/// Internal trait used for converting the `prometheus-http-query` crate's query result into the internal result type
pub(crate) trait IntoQryResult {
fn into_qry_result(self) -> QryResult;
}
impl IntoQryResult for Result<response::PromqlResult, error::Error> {
fn into_qry_result(self) -> QryResult {
self.map(|r| r.into()).map_err(|e| e.into())
}
}
/// A wrapper for possible result types of expression queries ([`Client::query`](crate::Client::query) and [`Client::query_range`](crate::Client::query_range)).
#[derive(Clone, Debug, EnumAsInner)]
pub enum Data {
Vector(Vec<response::InstantVector>),
Matrix(Vec<response::RangeVector>),
Scalar(response::Sample),
}
impl From<response::PromqlResult> for Data {
fn from(value: response::PromqlResult) -> Self {
let (data, _) = value.into_inner();
match data {
response::Data::Vector(v) => Data::Vector(v),
response::Data::Matrix(m) => Data::Matrix(m),
response::Data::Scalar(s) => Data::Scalar(s),
}
}
}
impl Data {
/// This is a shortcut to check if the query returned any data at all regardless of the exact type.
pub fn is_empty(&self) -> bool {
match self {
Data::Vector(v) => v.is_empty(),
Data::Matrix(v) => v.is_empty(),
Data::Scalar(_) => false,
}
}
}
/// A global error enum that contains all errors that are returned by this
/// library. Some errors are wrappers for errors from underlying libraries.
/// All errors (this enum as well as all contained structs) implement [`std::error::Error`].
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum Error {
/// Wraps errors from the underlying [`reqwest::Client`] that cannot be mapped
/// to a more specific error type. Deserialization errors also fall into this
/// category.
Client(ClientError),
/// Occurs when Prometheus responds with e.g. HTTP 4xx (e.g. due to a syntax error in a PromQL query).<br>
/// Details on the error as reported by Prometheus are included in [`PrometheusError`].
Prometheus(PrometheusError),
/// Occurs when the [`Client::series`](crate::Client::series) method is called with an empty set of
/// series [`Selector`](crate::selector::Selector)s. According to the Prometheus API description at least one
/// [`Selector`](crate::selector::Selector) must be provided.
EmptySeriesSelector,
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Client(e) => e.fmt(f),
Self::Prometheus(e) => e.fmt(f),
Self::EmptySeriesSelector => f.write_str("at least one series selector must be provided in order to query the series endpoint"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Client(e) => e.source(),
Self::Prometheus(p) => p.source(),
Self::EmptySeriesSelector => None,
}
}
}
impl From<prometheus_http_query::Error> for Error {
fn from(value: prometheus_http_query::Error) -> Self {
match value {
prometheus_http_query::Error::Client(c) => Self::Client(c.into()),
prometheus_http_query::Error::Prometheus(p) => Self::Prometheus(p.into()),
prometheus_http_query::Error::EmptySeriesSelector => Self::EmptySeriesSelector,
_ => unreachable!(),
}
}
}
/// This error is thrown when the JSON response's `status` field contains `error`.<br>
/// The error-related information from the JSON body is included in this error.
#[derive(Debug, Clone, PartialEq)]
pub struct PrometheusError {
pub(crate) error_type: PrometheusErrorType,
pub(crate) message: String,
}
impl std::error::Error for PrometheusError {}
impl fmt::Display for PrometheusError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.error_type, self.message)
}
}
impl From<error::PrometheusError> for PrometheusError {
fn from(value: error::PrometheusError) -> Self {
let error_type = PrometheusErrorType::from(value.error_type());
let message = value.message().to_owned();
PrometheusError {
error_type,
message,
}
}
}
impl PrometheusError {
/// Returns the parsed version of the error type that was given by the Prometheus API.
pub fn error_type(&self) -> PrometheusErrorType {
self.error_type
}
/// Returns the error message that was given by the Prometheus API.
pub fn message(&self) -> &str {
&self.message
}
pub fn is_timeout(&self) -> bool {
self.error_type == PrometheusErrorType::Timeout
}
pub fn is_canceled(&self) -> bool {
self.error_type == PrometheusErrorType::Canceled
}
pub fn is_execution(&self) -> bool {
self.error_type == PrometheusErrorType::Execution
}
pub fn is_bad_data(&self) -> bool {
self.error_type == PrometheusErrorType::BadData
}
pub fn is_internal(&self) -> bool {
self.error_type == PrometheusErrorType::Internal
}
pub fn is_unavailable(&self) -> bool {
self.error_type == PrometheusErrorType::Unavailable
}
pub fn is_not_found(&self) -> bool {
self.error_type == PrometheusErrorType::NotFound
}
}
/// The parsed error type as returned by the Prometheus API.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PrometheusErrorType {
Timeout,
Canceled,
Execution,
BadData,
Internal,
Unavailable,
NotFound,
}
impl From<error::PrometheusErrorType> for PrometheusErrorType {
fn from(value: error::PrometheusErrorType) -> Self {
match value {
error::PrometheusErrorType::Timeout => PrometheusErrorType::Timeout,
error::PrometheusErrorType::Canceled => PrometheusErrorType::Canceled,
error::PrometheusErrorType::Execution => PrometheusErrorType::Execution,
error::PrometheusErrorType::BadData => PrometheusErrorType::BadData,
error::PrometheusErrorType::Internal => PrometheusErrorType::Internal,
error::PrometheusErrorType::Unavailable => PrometheusErrorType::Unavailable,
error::PrometheusErrorType::NotFound => PrometheusErrorType::NotFound,
_ => unreachable!(),
}
}
}
impl fmt::Display for PrometheusErrorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Timeout => f.write_str("timeout"),
Self::Canceled => f.write_str("canceled"),
Self::Execution => f.write_str("execution"),
Self::BadData => f.write_str("bad_data"),
Self::Internal => f.write_str("internal"),
Self::Unavailable => f.write_str("unavailable"),
Self::NotFound => f.write_str("not_found"),
}
}
}
/// Is thrown when the [`Client`](crate::Client) or the underlying
/// [`reqwest::Error`] fail to build or execute a request.
#[derive(Debug, Clone)]
pub struct ClientError(String);
impl From<prometheus_http_query::error::ClientError> for ClientError {
fn from(value: prometheus_http_query::error::ClientError) -> Self {
let message = value.to_string();
ClientError(message)
}
}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for ClientError {}
use mquery::{query::IntoQuery, QueryManager}; use mquery::{query::IntoQuery, result::QryResult, QueryManager};
use prometheus_http_query::{response::PromqlResult, Error};
// local victoria metrics server // local victoria metrics server
pub static URL: &str = "http://localhost:8428"; pub static URL: &str = "http://localhost:8428";
pub async fn send_query(query: impl IntoQuery) -> Result<PromqlResult, Error> { pub async fn send_query(query: impl IntoQuery) -> QryResult {
QueryManager::new(URL.parse().unwrap()).query(query).await QueryManager::new(URL.parse().unwrap()).query(query).await
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment