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 serde::Deserialize;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use snafu::Snafu;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tower::Layer;
|
||||
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")]
|
||||
mod heappy;
|
||||
|
@ -20,6 +23,7 @@ mod heappy;
|
|||
#[cfg(feature = "pprof")]
|
||||
mod pprof;
|
||||
|
||||
pub mod error;
|
||||
pub mod metrics;
|
||||
pub mod utils;
|
||||
pub mod write;
|
||||
|
@ -62,23 +66,23 @@ pub enum ApplicationError {
|
|||
#[snafu(display("pprof support is not compiled"))]
|
||||
PProfIsNotCompiled,
|
||||
|
||||
#[snafu(display("Route error from run mode: {}", source))]
|
||||
RunModeRouteError { source: Box<dyn RouteError> },
|
||||
#[snafu(display("Route error from run mode: {}", e))]
|
||||
RunModeRouteError { e: Box<dyn HttpApiErrorSource> },
|
||||
}
|
||||
|
||||
impl RouteError for ApplicationError {
|
||||
fn response(&self) -> Response<Body> {
|
||||
impl HttpApiErrorSource for ApplicationError {
|
||||
fn to_http_api_error(&self) -> HttpApiError {
|
||||
match self {
|
||||
Self::InvalidQueryString { .. } => self.bad_request(),
|
||||
Self::PProf { .. } => self.internal_error(),
|
||||
Self::Prost { .. } => self.internal_error(),
|
||||
Self::ProstIO { .. } => self.internal_error(),
|
||||
Self::EmptyFlamegraph => self.no_content(),
|
||||
Self::HeappyIsNotCompiled => self.internal_error(),
|
||||
Self::PProfIsNotCompiled => self.internal_error(),
|
||||
e @ Self::InvalidQueryString { .. } => e.invalid(),
|
||||
e @ Self::PProf { .. } => e.internal_error(),
|
||||
e @ Self::Prost { .. } => e.internal_error(),
|
||||
e @ Self::ProstIO { .. } => e.internal_error(),
|
||||
e @ Self::EmptyFlamegraph => e.empty_value(),
|
||||
e @ Self::HeappyIsNotCompiled => e.internal_error(),
|
||||
e @ Self::PProfIsNotCompiled => e.internal_error(),
|
||||
#[cfg(feature = "heappy")]
|
||||
Self::HeappyError { .. } => self.internal_error(),
|
||||
Self::RunModeRouteError { source } => source.response(),
|
||||
e @ Self::HeappyError { .. } => e.internal_error(),
|
||||
Self::RunModeRouteError { e } => e.to_http_api_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,8 +139,7 @@ where
|
|||
_ => server_type
|
||||
.route_http_request(req)
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as _)
|
||||
.context(RunModeRouteError),
|
||||
.map_err(|e| ApplicationError::RunModeRouteError { e: Box::new(e) }),
|
||||
};
|
||||
|
||||
// TODO: Move logging to TraceLayer
|
||||
|
@ -146,7 +149,12 @@ where
|
|||
Ok(response)
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -236,6 +244,8 @@ impl PProfAllocsArgs {
|
|||
#[cfg(feature = "pprof")]
|
||||
async fn pprof_profile(req: Request<Body>) -> Result<Response<Body>, ApplicationError> {
|
||||
use ::pprof::protos::Message;
|
||||
use snafu::ResultExt;
|
||||
|
||||
let query_string = req.uri().query().unwrap_or_default();
|
||||
let query: PProfArgs =
|
||||
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
|
||||
#[cfg(feature = "heappy")]
|
||||
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: PProfAllocsArgs =
|
||||
serde_urlencoded::from_str(query_string).context(InvalidQueryString { query_string })?;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use bytes::{Bytes, BytesMut};
|
||||
use futures::StreamExt;
|
||||
use http::header::CONTENT_ENCODING;
|
||||
use hyper::{Body, Response};
|
||||
use hyper::Body;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
use crate::influxdb_ioxd::server_type::RouteError;
|
||||
use super::error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource};
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Snafu)]
|
||||
|
@ -28,14 +28,14 @@ pub enum ParseBodyError {
|
|||
ClientHangup { source: hyper::Error },
|
||||
}
|
||||
|
||||
impl RouteError for ParseBodyError {
|
||||
fn response(&self) -> Response<Body> {
|
||||
impl HttpApiErrorSource for ParseBodyError {
|
||||
fn to_http_api_error(&self) -> HttpApiError {
|
||||
match self {
|
||||
Self::RequestSizeExceeded { .. } => self.bad_request(),
|
||||
Self::InvalidContentEncoding { .. } => self.bad_request(),
|
||||
Self::ReadingHeaderAsUtf8 { .. } => self.bad_request(),
|
||||
Self::ReadingBodyAsGzip { .. } => self.bad_request(),
|
||||
Self::ClientHangup { .. } => self.bad_request(),
|
||||
e @ Self::RequestSizeExceeded { .. } => e.invalid(),
|
||||
e @ Self::InvalidContentEncoding { .. } => e.invalid(),
|
||||
e @ Self::ReadingHeaderAsUtf8 { .. } => e.invalid(),
|
||||
e @ Self::ReadingBodyAsGzip { .. } => e.invalid(),
|
||||
e @ Self::ClientHangup { .. } => e.invalid(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ use observability_deps::tracing::debug;
|
|||
use serde::Deserialize;
|
||||
use snafu::{OptionExt, ResultExt, Snafu};
|
||||
|
||||
use crate::influxdb_ioxd::{
|
||||
http::utils::parse_body,
|
||||
server_type::{ApiErrorCode, RouteError, ServerType},
|
||||
};
|
||||
use crate::influxdb_ioxd::{http::utils::parse_body, server_type::ServerType};
|
||||
|
||||
use super::metrics::LineProtocolMetrics;
|
||||
use super::{
|
||||
error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||
metrics::LineProtocolMetrics,
|
||||
};
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Snafu)]
|
||||
|
@ -63,27 +63,17 @@ pub enum HttpWriteError {
|
|||
},
|
||||
}
|
||||
|
||||
impl RouteError for HttpWriteError {
|
||||
fn response(&self) -> Response<Body> {
|
||||
impl HttpApiErrorSource for HttpWriteError {
|
||||
fn to_http_api_error(&self) -> HttpApiError {
|
||||
match self {
|
||||
Self::BucketMappingError { .. } => self.internal_error(),
|
||||
Self::WritingPoints { .. } => self.internal_error(),
|
||||
Self::ExpectedQueryString { .. } => self.bad_request(),
|
||||
Self::InvalidQueryString { .. } => self.bad_request(),
|
||||
Self::ReadingBodyAsUtf8 { .. } => self.bad_request(),
|
||||
Self::ParsingLineProtocol { .. } => self.bad_request(),
|
||||
Self::DatabaseNotFound { .. } => self.not_found(),
|
||||
Self::ParseBody { source } => source.response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
e @ Self::BucketMappingError { .. } => e.internal_error(),
|
||||
e @ Self::WritingPoints { .. } => e.internal_error(),
|
||||
e @ Self::ExpectedQueryString { .. } => e.invalid(),
|
||||
e @ Self::InvalidQueryString { .. } => e.invalid(),
|
||||
e @ Self::ReadingBodyAsUtf8 { .. } => e.invalid(),
|
||||
e @ Self::ParsingLineProtocol { .. } => e.invalid(),
|
||||
e @ Self::DatabaseNotFound { .. } => e.not_found(),
|
||||
Self::ParseBody { source } => source.to_http_api_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,12 @@ use snafu::{OptionExt, ResultExt, Snafu};
|
|||
|
||||
use crate::influxdb_ioxd::{
|
||||
http::{
|
||||
error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||
metrics::LineProtocolMetrics,
|
||||
utils::parse_body,
|
||||
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse, WriteInfo},
|
||||
},
|
||||
planner::Planner,
|
||||
server_type::{ApiErrorCode, RouteError},
|
||||
};
|
||||
use dml::DmlWrite;
|
||||
use std::{
|
||||
|
@ -152,42 +152,30 @@ pub enum ApplicationError {
|
|||
|
||||
type Result<T, E = ApplicationError> = std::result::Result<T, E>;
|
||||
|
||||
impl RouteError for ApplicationError {
|
||||
fn response(&self) -> Response<Body> {
|
||||
impl HttpApiErrorSource for ApplicationError {
|
||||
fn to_http_api_error(&self) -> HttpApiError {
|
||||
match self {
|
||||
Self::BucketMappingError { .. } => self.internal_error(),
|
||||
Self::Query { .. } => self.internal_error(),
|
||||
Self::ExpectedQueryString { .. } => self.bad_request(),
|
||||
Self::InvalidQueryString { .. } => self.bad_request(),
|
||||
Self::ReadingBodyAsUtf8 { .. } => self.bad_request(),
|
||||
Self::ParsingDelete { .. } => self.bad_request(),
|
||||
Self::BuildingDeletePredicate { .. } => self.bad_request(),
|
||||
Self::ExecutingDelete { .. } => self.bad_request(),
|
||||
Self::RouteNotFound { .. } => self.not_found(),
|
||||
Self::DatabaseNameError { .. } => self.bad_request(),
|
||||
Self::DatabaseNotFound { .. } => self.not_found(),
|
||||
Self::CreatingResponse { .. } => self.internal_error(),
|
||||
Self::FormattingResult { .. } => self.internal_error(),
|
||||
Self::ParsingFormat { .. } => self.bad_request(),
|
||||
Self::Planning { .. } => self.bad_request(),
|
||||
Self::ServerIdNotSet => self.bad_request(),
|
||||
Self::ServerNotInitialized => self.bad_request(),
|
||||
Self::DatabaseNotInitialized { .. } => self.bad_request(),
|
||||
Self::InternalServerError => self.internal_error(),
|
||||
Self::ParseBody { source } => source.response(),
|
||||
Self::WriteError { source } => source.response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
e @ Self::BucketMappingError { .. } => e.internal_error(),
|
||||
e @ Self::Query { .. } => e.internal_error(),
|
||||
e @ Self::ExpectedQueryString { .. } => e.invalid(),
|
||||
e @ Self::InvalidQueryString { .. } => e.invalid(),
|
||||
e @ Self::ReadingBodyAsUtf8 { .. } => e.invalid(),
|
||||
e @ Self::ParsingDelete { .. } => e.invalid(),
|
||||
e @ Self::BuildingDeletePredicate { .. } => e.invalid(),
|
||||
e @ Self::ExecutingDelete { .. } => e.invalid(),
|
||||
e @ Self::RouteNotFound { .. } => e.not_found(),
|
||||
e @ Self::DatabaseNameError { .. } => e.invalid(),
|
||||
e @ Self::DatabaseNotFound { .. } => e.not_found(),
|
||||
e @ Self::CreatingResponse { .. } => e.internal_error(),
|
||||
e @ Self::FormattingResult { .. } => e.internal_error(),
|
||||
e @ Self::ParsingFormat { .. } => e.invalid(),
|
||||
e @ Self::Planning { .. } => e.invalid(),
|
||||
e @ Self::ServerIdNotSet => e.invalid(),
|
||||
e @ Self::ServerNotInitialized => e.invalid(),
|
||||
e @ Self::DatabaseNotInitialized { .. } => e.invalid(),
|
||||
e @ Self::InternalServerError => e.internal_error(),
|
||||
Self::ParseBody { source } => source.to_http_api_error(),
|
||||
Self::WriteError { source } => source.to_http_api_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -769,6 +757,29 @@ mod tests {
|
|||
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
|
||||
async fn run_query(db: Arc<Db>, query: &str) -> Vec<RecordBatch> {
|
||||
let ctx = db.new_query_context(None);
|
||||
|
|
|
@ -1,87 +1,17 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use hyper::{Body, Request, Response};
|
||||
use metric::Registry;
|
||||
use snafu::Snafu;
|
||||
use trace::TraceCollector;
|
||||
|
||||
use super::rpc::RpcBuilderInput;
|
||||
use crate::influxdb_ioxd::{http::error::HttpApiErrorSource, rpc::RpcBuilderInput};
|
||||
|
||||
pub mod common_state;
|
||||
pub mod database;
|
||||
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)]
|
||||
pub enum RpcError {
|
||||
#[snafu(display("gRPC transport error: {}{}", source, details))]
|
||||
|
@ -107,7 +37,7 @@ impl From<tonic::transport::Error> for RpcError {
|
|||
|
||||
#[async_trait]
|
||||
pub trait ServerType: std::fmt::Debug + Send + Sync + 'static {
|
||||
type RouteError: RouteError;
|
||||
type RouteError: HttpApiErrorSource;
|
||||
|
||||
fn metric_registry(&self) -> Arc<Registry>;
|
||||
|
||||
|
|
|
@ -6,12 +6,10 @@ use dml::DmlWrite;
|
|||
use hyper::{Body, Method, Request, Response};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
use crate::influxdb_ioxd::{
|
||||
http::{
|
||||
metrics::LineProtocolMetrics,
|
||||
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse},
|
||||
},
|
||||
server_type::RouteError,
|
||||
use crate::influxdb_ioxd::http::{
|
||||
error::{HttpApiError, HttpApiErrorExt, HttpApiErrorSource},
|
||||
metrics::LineProtocolMetrics,
|
||||
write::{HttpDrivenWrite, InnerWriteError, RequestOrResponse},
|
||||
};
|
||||
|
||||
use super::RouterServerType;
|
||||
|
@ -27,11 +25,11 @@ pub enum ApplicationError {
|
|||
},
|
||||
}
|
||||
|
||||
impl RouteError for ApplicationError {
|
||||
fn response(&self) -> http::Response<hyper::Body> {
|
||||
impl HttpApiErrorSource for ApplicationError {
|
||||
fn to_http_api_error(&self) -> HttpApiError {
|
||||
match self {
|
||||
Self::RouteNotFound { .. } => self.not_found(),
|
||||
Self::WriteError { source } => source.response(),
|
||||
e @ Self::RouteNotFound { .. } => e.not_found(),
|
||||
Self::WriteError { source } => source.to_http_api_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@ async fn test_http_error_messages() {
|
|||
.await
|
||||
.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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue