refactor: align HTTP error handling w/ cloud 2
Streamline HTTP error by using the same representation as cloud 2. Also ensure that only internal server errors result in an error-level log entry. Fixes #3075.pull/24376/head
parent
6f268f8260
commit
3667cb36fa
|
@ -0,0 +1,163 @@
|
||||||
|
use hyper::{Body, Response, StatusCode};
|
||||||
|
|
||||||
|
/// Constants used in API error codes.
|
||||||
|
///
|
||||||
|
/// See <https://docs.influxdata.com/influxdb/v2.1/api/#operation/PostWrite>.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum HttpApiErrorCode {
|
||||||
|
InternalError,
|
||||||
|
NotFound,
|
||||||
|
Conflict,
|
||||||
|
Invalid,
|
||||||
|
UnprocessableEntity,
|
||||||
|
EmptyValue,
|
||||||
|
Unavailable,
|
||||||
|
Forbidden,
|
||||||
|
TooManyRequests,
|
||||||
|
Unauthorized,
|
||||||
|
MethodNotAllowed,
|
||||||
|
RequestTooLarge,
|
||||||
|
UnsupportedMediaType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpApiErrorCode {
|
||||||
|
/// Get machine-readable text representation.
|
||||||
|
fn as_text(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::InternalError => "internal error",
|
||||||
|
Self::NotFound => "not found",
|
||||||
|
Self::Conflict => "conflict",
|
||||||
|
Self::Invalid => "invalid",
|
||||||
|
Self::UnprocessableEntity => "unprocessable entity",
|
||||||
|
Self::EmptyValue => "empty value",
|
||||||
|
Self::Unavailable => "unavailable",
|
||||||
|
Self::Forbidden => "forbidden",
|
||||||
|
Self::TooManyRequests => "too many requests",
|
||||||
|
Self::Unauthorized => "unauthorized",
|
||||||
|
Self::MethodNotAllowed => "method not allowed",
|
||||||
|
Self::RequestTooLarge => "request too large",
|
||||||
|
Self::UnsupportedMediaType => "unsupported media type",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get tonic HTTP status code.
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
Self::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Self::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
Self::Conflict => StatusCode::CONFLICT,
|
||||||
|
Self::Invalid => StatusCode::BAD_REQUEST,
|
||||||
|
Self::UnprocessableEntity => StatusCode::UNPROCESSABLE_ENTITY,
|
||||||
|
Self::EmptyValue => StatusCode::NO_CONTENT,
|
||||||
|
Self::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
Self::Forbidden => StatusCode::FORBIDDEN,
|
||||||
|
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
|
||||||
|
Self::Unauthorized => StatusCode::UNAUTHORIZED,
|
||||||
|
Self::MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED,
|
||||||
|
Self::RequestTooLarge => StatusCode::PAYLOAD_TOO_LARGE,
|
||||||
|
Self::UnsupportedMediaType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the code is an internal server error.
|
||||||
|
fn is_internal(&self) -> bool {
|
||||||
|
matches!(self, Self::InternalError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error that is compatible with the Influxdata Cloud 2 HTTP API.
|
||||||
|
///
|
||||||
|
/// See <https://docs.influxdata.com/influxdb/v2.1/api/#operation/PostWrite>.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HttpApiError {
|
||||||
|
/// Machine-readable error code.
|
||||||
|
code: HttpApiErrorCode,
|
||||||
|
|
||||||
|
/// Human-readable message.
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpApiError {
|
||||||
|
/// Create new error from code and message.
|
||||||
|
pub fn new(code: HttpApiErrorCode, msg: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
code,
|
||||||
|
msg: msg.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate response body for this error.
|
||||||
|
fn body(&self) -> Body {
|
||||||
|
let json = serde_json::json!({
|
||||||
|
"code": self.code.as_text().to_string(),
|
||||||
|
"message": self.msg.clone(),
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Body::from(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate response for this error.
|
||||||
|
pub fn response(&self) -> Response<Body> {
|
||||||
|
Response::builder()
|
||||||
|
.status(self.code.status_code())
|
||||||
|
.body(self.body())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the error is an internal server error.
|
||||||
|
pub fn is_internal(&self) -> bool {
|
||||||
|
self.code.is_internal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for HttpApiError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}: {}", self.code.as_text(), self.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for HttpApiError {}
|
||||||
|
|
||||||
|
/// Mixin-trait to simplify creation of [`HttpApiError`].
|
||||||
|
pub trait HttpApiErrorExt {
|
||||||
|
/// No data can be returned, but the server was asked to do so.
|
||||||
|
fn empty_value(&self) -> HttpApiError;
|
||||||
|
|
||||||
|
/// Internal server error. This is a bug / misconfiguration.
|
||||||
|
fn internal_error(&self) -> HttpApiError;
|
||||||
|
|
||||||
|
/// Invalid/bad request.
|
||||||
|
fn invalid(&self) -> HttpApiError;
|
||||||
|
|
||||||
|
/// Resource was not found.
|
||||||
|
fn not_found(&self) -> HttpApiError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> HttpApiErrorExt for E
|
||||||
|
where
|
||||||
|
E: std::error::Error,
|
||||||
|
{
|
||||||
|
fn empty_value(&self) -> HttpApiError {
|
||||||
|
HttpApiError::new(HttpApiErrorCode::EmptyValue, self.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal_error(&self) -> HttpApiError {
|
||||||
|
HttpApiError::new(HttpApiErrorCode::InternalError, self.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid(&self) -> HttpApiError {
|
||||||
|
HttpApiError::new(HttpApiErrorCode::Invalid, self.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_found(&self) -> HttpApiError {
|
||||||
|
HttpApiError::new(HttpApiErrorCode::NotFound, self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that can be transformed into a [`HttpApiError`].
|
||||||
|
pub trait HttpApiErrorSource: std::error::Error {
|
||||||
|
/// Create [`HttpApiError`].
|
||||||
|
fn to_http_api_error(&self) -> HttpApiError;
|
||||||
|
}
|
|
@ -7,12 +7,15 @@ use hyper::{
|
||||||
};
|
};
|
||||||
use observability_deps::tracing::{debug, error};
|
use observability_deps::tracing::{debug, error};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::Snafu;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tower::Layer;
|
use tower::Layer;
|
||||||
use trace_http::{ctx::TraceHeaderParser, tower::TraceLayer};
|
use trace_http::{ctx::TraceHeaderParser, tower::TraceLayer};
|
||||||
|
|
||||||
use crate::influxdb_ioxd::server_type::{RouteError, ServerType};
|
use crate::influxdb_ioxd::{
|
||||||
|
http::error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||||
|
server_type::ServerType,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "heappy")]
|
#[cfg(feature = "heappy")]
|
||||||
mod heappy;
|
mod heappy;
|
||||||
|
@ -20,6 +23,7 @@ mod heappy;
|
||||||
#[cfg(feature = "pprof")]
|
#[cfg(feature = "pprof")]
|
||||||
mod pprof;
|
mod pprof;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
pub mod metrics;
|
pub mod metrics;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod write;
|
pub mod write;
|
||||||
|
@ -62,23 +66,23 @@ pub enum ApplicationError {
|
||||||
#[snafu(display("pprof support is not compiled"))]
|
#[snafu(display("pprof support is not compiled"))]
|
||||||
PProfIsNotCompiled,
|
PProfIsNotCompiled,
|
||||||
|
|
||||||
#[snafu(display("Route error from run mode: {}", source))]
|
#[snafu(display("Route error from run mode: {}", e))]
|
||||||
RunModeRouteError { source: Box<dyn RouteError> },
|
RunModeRouteError { e: Box<dyn HttpApiErrorSource> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteError for ApplicationError {
|
impl HttpApiErrorSource for ApplicationError {
|
||||||
fn response(&self) -> Response<Body> {
|
fn to_http_api_error(&self) -> HttpApiError {
|
||||||
match self {
|
match self {
|
||||||
Self::InvalidQueryString { .. } => self.bad_request(),
|
e @ Self::InvalidQueryString { .. } => e.invalid(),
|
||||||
Self::PProf { .. } => self.internal_error(),
|
e @ Self::PProf { .. } => e.internal_error(),
|
||||||
Self::Prost { .. } => self.internal_error(),
|
e @ Self::Prost { .. } => e.internal_error(),
|
||||||
Self::ProstIO { .. } => self.internal_error(),
|
e @ Self::ProstIO { .. } => e.internal_error(),
|
||||||
Self::EmptyFlamegraph => self.no_content(),
|
e @ Self::EmptyFlamegraph => e.empty_value(),
|
||||||
Self::HeappyIsNotCompiled => self.internal_error(),
|
e @ Self::HeappyIsNotCompiled => e.internal_error(),
|
||||||
Self::PProfIsNotCompiled => self.internal_error(),
|
e @ Self::PProfIsNotCompiled => e.internal_error(),
|
||||||
#[cfg(feature = "heappy")]
|
#[cfg(feature = "heappy")]
|
||||||
Self::HeappyError { .. } => self.internal_error(),
|
e @ Self::HeappyError { .. } => e.internal_error(),
|
||||||
Self::RunModeRouteError { source } => source.response(),
|
Self::RunModeRouteError { e } => e.to_http_api_error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,8 +139,7 @@ where
|
||||||
_ => server_type
|
_ => server_type
|
||||||
.route_http_request(req)
|
.route_http_request(req)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Box::new(e) as _)
|
.map_err(|e| ApplicationError::RunModeRouteError { e: Box::new(e) }),
|
||||||
.context(RunModeRouteError),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Move logging to TraceLayer
|
// TODO: Move logging to TraceLayer
|
||||||
|
@ -146,7 +149,12 @@ where
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!(%error, %method, %uri, ?content_length, "Error while handling request");
|
let error: HttpApiError = error.to_http_api_error();
|
||||||
|
if error.is_internal() {
|
||||||
|
error!(%error, %method, %uri, ?content_length, "Error while handling request");
|
||||||
|
} else {
|
||||||
|
debug!(%error, %method, %uri, ?content_length, "Error while handling request");
|
||||||
|
}
|
||||||
Ok(error.response())
|
Ok(error.response())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +244,8 @@ impl PProfAllocsArgs {
|
||||||
#[cfg(feature = "pprof")]
|
#[cfg(feature = "pprof")]
|
||||||
async fn pprof_profile(req: Request<Body>) -> Result<Response<Body>, ApplicationError> {
|
async fn pprof_profile(req: Request<Body>) -> Result<Response<Body>, ApplicationError> {
|
||||||
use ::pprof::protos::Message;
|
use ::pprof::protos::Message;
|
||||||
|
use snafu::ResultExt;
|
||||||
|
|
||||||
let query_string = req.uri().query().unwrap_or_default();
|
let query_string = req.uri().query().unwrap_or_default();
|
||||||
let query: PProfArgs =
|
let query: PProfArgs =
|
||||||
serde_urlencoded::from_str(query_string).context(InvalidQueryString { query_string })?;
|
serde_urlencoded::from_str(query_string).context(InvalidQueryString { query_string })?;
|
||||||
|
@ -282,6 +292,8 @@ async fn pprof_profile(_req: Request<Body>) -> Result<Response<Body>, Applicatio
|
||||||
// If heappy support is enabled, call it
|
// If heappy support is enabled, call it
|
||||||
#[cfg(feature = "heappy")]
|
#[cfg(feature = "heappy")]
|
||||||
async fn pprof_heappy_profile(req: Request<Body>) -> Result<Response<Body>, ApplicationError> {
|
async fn pprof_heappy_profile(req: Request<Body>) -> Result<Response<Body>, ApplicationError> {
|
||||||
|
use snafu::ResultExt;
|
||||||
|
|
||||||
let query_string = req.uri().query().unwrap_or_default();
|
let query_string = req.uri().query().unwrap_or_default();
|
||||||
let query: PProfAllocsArgs =
|
let query: PProfAllocsArgs =
|
||||||
serde_urlencoded::from_str(query_string).context(InvalidQueryString { query_string })?;
|
serde_urlencoded::from_str(query_string).context(InvalidQueryString { query_string })?;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use http::header::CONTENT_ENCODING;
|
use http::header::CONTENT_ENCODING;
|
||||||
use hyper::{Body, Response};
|
use hyper::Body;
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
|
|
||||||
use crate::influxdb_ioxd::server_type::RouteError;
|
use super::error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource};
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
|
@ -28,14 +28,14 @@ pub enum ParseBodyError {
|
||||||
ClientHangup { source: hyper::Error },
|
ClientHangup { source: hyper::Error },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteError for ParseBodyError {
|
impl HttpApiErrorSource for ParseBodyError {
|
||||||
fn response(&self) -> Response<Body> {
|
fn to_http_api_error(&self) -> HttpApiError {
|
||||||
match self {
|
match self {
|
||||||
Self::RequestSizeExceeded { .. } => self.bad_request(),
|
e @ Self::RequestSizeExceeded { .. } => e.invalid(),
|
||||||
Self::InvalidContentEncoding { .. } => self.bad_request(),
|
e @ Self::InvalidContentEncoding { .. } => e.invalid(),
|
||||||
Self::ReadingHeaderAsUtf8 { .. } => self.bad_request(),
|
e @ Self::ReadingHeaderAsUtf8 { .. } => e.invalid(),
|
||||||
Self::ReadingBodyAsGzip { .. } => self.bad_request(),
|
e @ Self::ReadingBodyAsGzip { .. } => e.invalid(),
|
||||||
Self::ClientHangup { .. } => self.bad_request(),
|
e @ Self::ClientHangup { .. } => e.invalid(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ use observability_deps::tracing::debug;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use snafu::{OptionExt, ResultExt, Snafu};
|
use snafu::{OptionExt, ResultExt, Snafu};
|
||||||
|
|
||||||
use crate::influxdb_ioxd::{
|
use crate::influxdb_ioxd::{http::utils::parse_body, server_type::ServerType};
|
||||||
http::utils::parse_body,
|
|
||||||
server_type::{ApiErrorCode, RouteError, ServerType},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::metrics::LineProtocolMetrics;
|
use super::{
|
||||||
|
error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||||
|
metrics::LineProtocolMetrics,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
|
@ -63,27 +63,17 @@ pub enum HttpWriteError {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteError for HttpWriteError {
|
impl HttpApiErrorSource for HttpWriteError {
|
||||||
fn response(&self) -> Response<Body> {
|
fn to_http_api_error(&self) -> HttpApiError {
|
||||||
match self {
|
match self {
|
||||||
Self::BucketMappingError { .. } => self.internal_error(),
|
e @ Self::BucketMappingError { .. } => e.internal_error(),
|
||||||
Self::WritingPoints { .. } => self.internal_error(),
|
e @ Self::WritingPoints { .. } => e.internal_error(),
|
||||||
Self::ExpectedQueryString { .. } => self.bad_request(),
|
e @ Self::ExpectedQueryString { .. } => e.invalid(),
|
||||||
Self::InvalidQueryString { .. } => self.bad_request(),
|
e @ Self::InvalidQueryString { .. } => e.invalid(),
|
||||||
Self::ReadingBodyAsUtf8 { .. } => self.bad_request(),
|
e @ Self::ReadingBodyAsUtf8 { .. } => e.invalid(),
|
||||||
Self::ParsingLineProtocol { .. } => self.bad_request(),
|
e @ Self::ParsingLineProtocol { .. } => e.invalid(),
|
||||||
Self::DatabaseNotFound { .. } => self.not_found(),
|
e @ Self::DatabaseNotFound { .. } => e.not_found(),
|
||||||
Self::ParseBody { source } => source.response(),
|
Self::ParseBody { source } => source.to_http_api_error(),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map the error type into an API error code.
|
|
||||||
fn api_error_code(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::DatabaseNotFound { .. } => ApiErrorCode::DB_NOT_FOUND.into(),
|
|
||||||
Self::ParseBody { source } => source.api_error_code(),
|
|
||||||
// A "catch all" error code
|
|
||||||
_ => ApiErrorCode::UNKNOWN.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,12 @@ use snafu::{OptionExt, ResultExt, Snafu};
|
||||||
|
|
||||||
use crate::influxdb_ioxd::{
|
use crate::influxdb_ioxd::{
|
||||||
http::{
|
http::{
|
||||||
|
error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||||
metrics::LineProtocolMetrics,
|
metrics::LineProtocolMetrics,
|
||||||
utils::parse_body,
|
utils::parse_body,
|
||||||
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse, WriteInfo},
|
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse, WriteInfo},
|
||||||
},
|
},
|
||||||
planner::Planner,
|
planner::Planner,
|
||||||
server_type::{ApiErrorCode, RouteError},
|
|
||||||
};
|
};
|
||||||
use dml::DmlWrite;
|
use dml::DmlWrite;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -152,42 +152,30 @@ pub enum ApplicationError {
|
||||||
|
|
||||||
type Result<T, E = ApplicationError> = std::result::Result<T, E>;
|
type Result<T, E = ApplicationError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
impl RouteError for ApplicationError {
|
impl HttpApiErrorSource for ApplicationError {
|
||||||
fn response(&self) -> Response<Body> {
|
fn to_http_api_error(&self) -> HttpApiError {
|
||||||
match self {
|
match self {
|
||||||
Self::BucketMappingError { .. } => self.internal_error(),
|
e @ Self::BucketMappingError { .. } => e.internal_error(),
|
||||||
Self::Query { .. } => self.internal_error(),
|
e @ Self::Query { .. } => e.internal_error(),
|
||||||
Self::ExpectedQueryString { .. } => self.bad_request(),
|
e @ Self::ExpectedQueryString { .. } => e.invalid(),
|
||||||
Self::InvalidQueryString { .. } => self.bad_request(),
|
e @ Self::InvalidQueryString { .. } => e.invalid(),
|
||||||
Self::ReadingBodyAsUtf8 { .. } => self.bad_request(),
|
e @ Self::ReadingBodyAsUtf8 { .. } => e.invalid(),
|
||||||
Self::ParsingDelete { .. } => self.bad_request(),
|
e @ Self::ParsingDelete { .. } => e.invalid(),
|
||||||
Self::BuildingDeletePredicate { .. } => self.bad_request(),
|
e @ Self::BuildingDeletePredicate { .. } => e.invalid(),
|
||||||
Self::ExecutingDelete { .. } => self.bad_request(),
|
e @ Self::ExecutingDelete { .. } => e.invalid(),
|
||||||
Self::RouteNotFound { .. } => self.not_found(),
|
e @ Self::RouteNotFound { .. } => e.not_found(),
|
||||||
Self::DatabaseNameError { .. } => self.bad_request(),
|
e @ Self::DatabaseNameError { .. } => e.invalid(),
|
||||||
Self::DatabaseNotFound { .. } => self.not_found(),
|
e @ Self::DatabaseNotFound { .. } => e.not_found(),
|
||||||
Self::CreatingResponse { .. } => self.internal_error(),
|
e @ Self::CreatingResponse { .. } => e.internal_error(),
|
||||||
Self::FormattingResult { .. } => self.internal_error(),
|
e @ Self::FormattingResult { .. } => e.internal_error(),
|
||||||
Self::ParsingFormat { .. } => self.bad_request(),
|
e @ Self::ParsingFormat { .. } => e.invalid(),
|
||||||
Self::Planning { .. } => self.bad_request(),
|
e @ Self::Planning { .. } => e.invalid(),
|
||||||
Self::ServerIdNotSet => self.bad_request(),
|
e @ Self::ServerIdNotSet => e.invalid(),
|
||||||
Self::ServerNotInitialized => self.bad_request(),
|
e @ Self::ServerNotInitialized => e.invalid(),
|
||||||
Self::DatabaseNotInitialized { .. } => self.bad_request(),
|
e @ Self::DatabaseNotInitialized { .. } => e.invalid(),
|
||||||
Self::InternalServerError => self.internal_error(),
|
e @ Self::InternalServerError => e.internal_error(),
|
||||||
Self::ParseBody { source } => source.response(),
|
Self::ParseBody { source } => source.to_http_api_error(),
|
||||||
Self::WriteError { source } => source.response(),
|
Self::WriteError { source } => source.to_http_api_error(),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map the error type into an API error code.
|
|
||||||
fn api_error_code(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::DatabaseNameError { .. } => ApiErrorCode::DB_INVALID_NAME.into(),
|
|
||||||
Self::DatabaseNotFound { .. } => ApiErrorCode::DB_NOT_FOUND.into(),
|
|
||||||
Self::ParseBody { source } => source.api_error_code(),
|
|
||||||
Self::WriteError { source } => source.api_error_code(),
|
|
||||||
// A "catch all" error code
|
|
||||||
_ => ApiErrorCode::UNKNOWN.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -769,6 +757,29 @@ mod tests {
|
||||||
check_response("query", response, StatusCode::OK, Some(res)).await;
|
check_response("query", response, StatusCode::OK, Some(res)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_query_invalid_name() {
|
||||||
|
let (client, test_server) = setup_test_data().await;
|
||||||
|
|
||||||
|
// send query data
|
||||||
|
let response = client
|
||||||
|
.get(&format!(
|
||||||
|
"{}/api/v3/query?d=&q={}",
|
||||||
|
test_server.url(),
|
||||||
|
"select%20*%20from%20h2o_temperature%20order%20by%20surface_degrees"
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
check_response(
|
||||||
|
"query",
|
||||||
|
response,
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Some(r#"{"code":"invalid","message":"Invalid database name: Database name length must be between 1 and 64 characters"}"#),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
/// Run the specified SQL query and return formatted results as a string
|
/// Run the specified SQL query and return formatted results as a string
|
||||||
async fn run_query(db: Arc<Db>, query: &str) -> Vec<RecordBatch> {
|
async fn run_query(db: Arc<Db>, query: &str) -> Vec<RecordBatch> {
|
||||||
let ctx = db.new_query_context(None);
|
let ctx = db.new_query_context(None);
|
||||||
|
|
|
@ -1,87 +1,17 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use hyper::{Body, Request, Response, StatusCode};
|
use hyper::{Body, Request, Response};
|
||||||
use metric::Registry;
|
use metric::Registry;
|
||||||
use snafu::Snafu;
|
use snafu::Snafu;
|
||||||
use trace::TraceCollector;
|
use trace::TraceCollector;
|
||||||
|
|
||||||
use super::rpc::RpcBuilderInput;
|
use crate::influxdb_ioxd::{http::error::HttpApiErrorSource, rpc::RpcBuilderInput};
|
||||||
|
|
||||||
pub mod common_state;
|
pub mod common_state;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
|
|
||||||
/// Constants used in API error codes.
|
|
||||||
///
|
|
||||||
/// Expressing this as a enum prevents reuse of discriminants, and as they're
|
|
||||||
/// effectively consts this uses UPPER_SNAKE_CASE.
|
|
||||||
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum ApiErrorCode {
|
|
||||||
/// An unknown/unhandled error
|
|
||||||
UNKNOWN = 100,
|
|
||||||
|
|
||||||
/// The database name in the request is invalid.
|
|
||||||
DB_INVALID_NAME = 101,
|
|
||||||
|
|
||||||
/// The database referenced already exists.
|
|
||||||
DB_ALREADY_EXISTS = 102,
|
|
||||||
|
|
||||||
/// The database referenced does not exist.
|
|
||||||
DB_NOT_FOUND = 103,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ApiErrorCode> for u32 {
|
|
||||||
fn from(v: ApiErrorCode) -> Self {
|
|
||||||
v as Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait RouteError: std::error::Error + snafu::AsErrorSource {
|
|
||||||
fn response(&self) -> Response<Body>;
|
|
||||||
|
|
||||||
fn bad_request(&self) -> Response<Body> {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::BAD_REQUEST)
|
|
||||||
.body(self.body())
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn internal_error(&self) -> Response<Body> {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
.body(self.body())
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn not_found(&self) -> Response<Body> {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::NOT_FOUND)
|
|
||||||
.body(Body::empty())
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_content(&self) -> Response<Body> {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::NO_CONTENT)
|
|
||||||
.body(self.body())
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn body(&self) -> Body {
|
|
||||||
let json =
|
|
||||||
serde_json::json!({"error": self.to_string(), "error_code": self.api_error_code()})
|
|
||||||
.to_string();
|
|
||||||
Body::from(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map the error type into an API error code.
|
|
||||||
fn api_error_code(&self) -> u32 {
|
|
||||||
ApiErrorCode::UNKNOWN.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
pub enum RpcError {
|
pub enum RpcError {
|
||||||
#[snafu(display("gRPC transport error: {}{}", source, details))]
|
#[snafu(display("gRPC transport error: {}{}", source, details))]
|
||||||
|
@ -107,7 +37,7 @@ impl From<tonic::transport::Error> for RpcError {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ServerType: std::fmt::Debug + Send + Sync + 'static {
|
pub trait ServerType: std::fmt::Debug + Send + Sync + 'static {
|
||||||
type RouteError: RouteError;
|
type RouteError: HttpApiErrorSource;
|
||||||
|
|
||||||
fn metric_registry(&self) -> Arc<Registry>;
|
fn metric_registry(&self) -> Arc<Registry>;
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,10 @@ use dml::DmlWrite;
|
||||||
use hyper::{Body, Method, Request, Response};
|
use hyper::{Body, Method, Request, Response};
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
|
|
||||||
use crate::influxdb_ioxd::{
|
use crate::influxdb_ioxd::http::{
|
||||||
http::{
|
error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||||
metrics::LineProtocolMetrics,
|
metrics::LineProtocolMetrics,
|
||||||
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse},
|
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse},
|
||||||
},
|
|
||||||
server_type::RouteError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RouterServerType;
|
use super::RouterServerType;
|
||||||
|
@ -27,11 +25,11 @@ pub enum ApplicationError {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteError for ApplicationError {
|
impl HttpApiErrorSource for ApplicationError {
|
||||||
fn response(&self) -> http::Response<hyper::Body> {
|
fn to_http_api_error(&self) -> HttpApiError {
|
||||||
match self {
|
match self {
|
||||||
Self::RouteNotFound { .. } => self.not_found(),
|
e @ Self::RouteNotFound { .. } => e.not_found(),
|
||||||
Self::WriteError { source } => source.response(),
|
Self::WriteError { source } => source.to_http_api_error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,6 @@ async fn test_http_error_messages() {
|
||||||
.await
|
.await
|
||||||
.expect_err("Should have errored");
|
.expect_err("Should have errored");
|
||||||
|
|
||||||
let expected_error = "HTTP request returned an error: 400 Bad Request, `{\"error\":\"Error parsing line protocol: error parsing line 1: A generic parsing error occurred: TakeWhile1\",\"error_code\":100}`";
|
let expected_error = r#"HTTP request returned an error: 400 Bad Request, `{"code":"invalid","message":"Error parsing line protocol: error parsing line 1: A generic parsing error occurred: TakeWhile1"}`"#;
|
||||||
assert_eq!(result.to_string(), expected_error);
|
assert_eq!(result.to_string(), expected_error);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue