From 10b564a4db8a46046bd405de95232235520bc0a1 Mon Sep 17 00:00:00 2001 From: Maaz Ahmed <maaz.a@subcom.tech> Date: Fri, 15 Dec 2023 11:23:11 +0530 Subject: [PATCH] refactor: replace external error type with an internal one in pub API --- src/lib.rs | 15 ++-- src/result.rs | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/utils.rs | 4 +- 3 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 src/result.rs diff --git a/src/lib.rs b/src/lib.rs index baaef71..dbefb59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ #![doc = include_str!("../README.md")] pub mod query; +pub mod result; -use prometheus_http_query::{response::PromqlResult, Client, Error}; +use prometheus_http_query::{response::PromqlResult, Client}; use query::IntoQuery; use reqwest::header::HeaderValue; use url::Url; @@ -65,7 +66,7 @@ impl QueryManager { /// The trait is implemented for `&str`, `String`, `&String`, and __mquery's__ /// internal types such as [`query::Metric`], [`query::Scalar`] and others that result /// 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) -> Result<PromqlResult, result::Error> { let mut builder = self.client.query(query.into_query().as_ref()); if let Some((name, val)) = self.auth.get_header() { builder = builder.header(name, val); @@ -74,8 +75,8 @@ impl QueryManager { builder = builder.timeout(*timeout); } match self.method { - Method::Get => builder.get().await, - Method::Post => builder.post().await, + Method::Get => builder.get().await.map_err(|e| e.into()), + Method::Post => builder.post().await.map_err(|e| e.into()), } } @@ -91,7 +92,7 @@ impl QueryManager { start: i64, end: i64, step: f64, - ) -> Result<PromqlResult, Error> { + ) -> Result<PromqlResult, result::Error> { let mut builder = self .client .query_range(query.into_query().as_ref(), start, end, step); @@ -102,8 +103,8 @@ impl QueryManager { builder = builder.timeout(*timeout); } match self.method { - Method::Get => builder.get().await, - Method::Post => builder.post().await, + Method::Get => builder.get().await.map_err(|e| e.into()), + Method::Post => builder.post().await.map_err(|e| e.into()), } } } diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..fac15e8 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,189 @@ +//! `PromqlResult` 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`. +//! +//! The 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 prometheus_http_query::error; +use std::fmt::{self, Display}; + +/// 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 {} diff --git a/tests/utils.rs b/tests/utils.rs index dbaa3a8..12303de 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -1,9 +1,9 @@ use mquery::{query::IntoQuery, QueryManager}; -use prometheus_http_query::{response::PromqlResult, Error}; +use prometheus_http_query::response::PromqlResult; // local victoria metrics server 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) -> Result<PromqlResult, mquery::result::Error> { QueryManager::new(URL.parse().unwrap()).query(query).await } -- GitLab