servers/
error.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::net::SocketAddr;
17use std::string::FromUtf8Error;
18
19use axum::http::StatusCode as HttpStatusCode;
20use axum::response::{IntoResponse, Response};
21use axum::{Json, http};
22use base64::DecodeError;
23use common_base::readable_size::ReadableSize;
24use common_error::define_into_tonic_status;
25use common_error::ext::{BoxedError, ErrorExt};
26use common_error::status_code::StatusCode;
27use common_macro::stack_trace_debug;
28use common_telemetry::{error, warn};
29use datafusion::error::DataFusionError;
30use datatypes::prelude::ConcreteDataType;
31use headers::ContentType;
32use http::header::InvalidHeaderValue;
33use query::parser::PromQuery;
34use serde_json::json;
35use snafu::{Location, Snafu};
36
37#[derive(Snafu)]
38#[snafu(visibility(pub))]
39#[stack_trace_debug]
40pub enum Error {
41    #[snafu(display("Failed to bind address: {}", addr))]
42    AddressBind {
43        addr: SocketAddr,
44        #[snafu(source)]
45        error: std::io::Error,
46        #[snafu(implicit)]
47        location: Location,
48    },
49
50    #[snafu(display("Arrow error"))]
51    Arrow {
52        #[snafu(source)]
53        error: arrow_schema::ArrowError,
54    },
55
56    #[snafu(display("Internal error: {}", err_msg))]
57    Internal { err_msg: String },
58
59    #[snafu(display("Unsupported data type: {}, reason: {}", data_type, reason))]
60    UnsupportedDataType {
61        data_type: ConcreteDataType,
62        reason: String,
63    },
64
65    #[snafu(display("Internal IO error"))]
66    InternalIo {
67        #[snafu(source)]
68        error: std::io::Error,
69    },
70
71    #[snafu(display("Tokio IO error: {}", err_msg))]
72    TokioIo {
73        err_msg: String,
74        #[snafu(source)]
75        error: std::io::Error,
76    },
77
78    #[snafu(display("Failed to collect recordbatch"))]
79    CollectRecordbatch {
80        #[snafu(implicit)]
81        location: Location,
82        source: common_recordbatch::error::Error,
83    },
84
85    #[snafu(display("Failed to start HTTP server"))]
86    StartHttp {
87        #[snafu(source)]
88        error: hyper::Error,
89    },
90
91    #[snafu(display("Failed to start gRPC server"))]
92    StartGrpc {
93        #[snafu(source)]
94        error: tonic::transport::Error,
95    },
96
97    #[snafu(display("Request memory limit exceeded"))]
98    MemoryLimitExceeded {
99        #[snafu(implicit)]
100        location: Location,
101        source: common_memory_manager::Error,
102    },
103
104    #[snafu(display("{} server is already started", server))]
105    AlreadyStarted {
106        server: String,
107        #[snafu(implicit)]
108        location: Location,
109    },
110
111    #[snafu(display("Failed to bind address {}", addr))]
112    TcpBind {
113        addr: SocketAddr,
114        #[snafu(source)]
115        error: std::io::Error,
116    },
117
118    #[snafu(display("Failed to execute query"))]
119    ExecuteQuery {
120        #[snafu(implicit)]
121        location: Location,
122        source: BoxedError,
123    },
124
125    #[snafu(display("Failed to execute plan"))]
126    ExecutePlan {
127        #[snafu(implicit)]
128        location: Location,
129        source: BoxedError,
130    },
131
132    #[snafu(display("Execute gRPC query error"))]
133    ExecuteGrpcQuery {
134        #[snafu(implicit)]
135        location: Location,
136        source: BoxedError,
137    },
138
139    #[snafu(display("Execute gRPC request error"))]
140    ExecuteGrpcRequest {
141        #[snafu(implicit)]
142        location: Location,
143        source: BoxedError,
144    },
145
146    #[snafu(display("Failed to check database validity"))]
147    CheckDatabaseValidity {
148        #[snafu(implicit)]
149        location: Location,
150        source: BoxedError,
151    },
152
153    #[snafu(display("Failed to describe statement"))]
154    DescribeStatement { source: BoxedError },
155
156    #[snafu(display("Pipeline error"))]
157    Pipeline {
158        #[snafu(source)]
159        source: pipeline::error::Error,
160        #[snafu(implicit)]
161        location: Location,
162    },
163
164    #[snafu(display("Not supported: {}", feat))]
165    NotSupported { feat: String },
166
167    #[snafu(display("Invalid request parameter: {}", reason))]
168    InvalidParameter {
169        reason: String,
170        #[snafu(implicit)]
171        location: Location,
172    },
173
174    #[snafu(display(
175        "Too many concurrent large requests, limit: {}, request size: {}",
176        ReadableSize(*limit as u64),
177        ReadableSize(*request_size as u64)
178    ))]
179    TooManyConcurrentRequests {
180        limit: usize,
181        request_size: usize,
182        #[snafu(implicit)]
183        location: Location,
184    },
185
186    #[snafu(display("Invalid query: {}", reason))]
187    InvalidQuery {
188        reason: String,
189        #[snafu(implicit)]
190        location: Location,
191    },
192
193    #[snafu(display("Failed to parse query"))]
194    FailedToParseQuery {
195        #[snafu(implicit)]
196        location: Location,
197        source: sql::error::Error,
198    },
199
200    #[snafu(display("Failed to parse InfluxDB line protocol"))]
201    InfluxdbLineProtocol {
202        #[snafu(implicit)]
203        location: Location,
204        #[snafu(source)]
205        error: influxdb_line_protocol::Error,
206    },
207
208    #[snafu(display("Failed to write row"))]
209    RowWriter {
210        #[snafu(implicit)]
211        location: Location,
212        source: common_grpc::error::Error,
213    },
214
215    #[snafu(display("Failed to convert time precision, name: {}", name))]
216    TimePrecision {
217        name: String,
218        #[snafu(implicit)]
219        location: Location,
220    },
221
222    #[snafu(display("Invalid OpenTSDB Json request"))]
223    InvalidOpentsdbJsonRequest {
224        #[snafu(source)]
225        error: serde_json::error::Error,
226        #[snafu(implicit)]
227        location: Location,
228    },
229
230    #[snafu(display("Failed to decode prometheus remote request"))]
231    DecodePromRemoteRequest {
232        #[snafu(implicit)]
233        location: Location,
234        #[snafu(source)]
235        error: prost::DecodeError,
236    },
237
238    #[snafu(display(
239        "Failed to decode OTLP request (content-type: {content_type}): {error}. The endpoint only accepts 'application/x-protobuf' format."
240    ))]
241    DecodeOtlpRequest {
242        content_type: String,
243        #[snafu(implicit)]
244        location: Location,
245        #[snafu(source)]
246        error: prost::DecodeError,
247    },
248
249    #[snafu(display("Failed to decode Loki request: {error}"))]
250    DecodeLokiRequest {
251        #[snafu(implicit)]
252        location: Location,
253        #[snafu(source)]
254        error: prost::DecodeError,
255    },
256
257    #[snafu(display(
258        "Unsupported content type 'application/json'. OTLP endpoint only supports 'application/x-protobuf'. Please configure your OTLP exporter to use protobuf encoding."
259    ))]
260    UnsupportedJsonContentType {
261        #[snafu(implicit)]
262        location: Location,
263    },
264
265    #[snafu(display(
266        "OTLP metric input have incompatible existing tables, please refer to docs for details"
267    ))]
268    OtlpMetricModeIncompatible {
269        #[snafu(implicit)]
270        location: Location,
271    },
272
273    #[snafu(display("Common Meta error"))]
274    CommonMeta {
275        #[snafu(implicit)]
276        location: Location,
277        #[snafu(source)]
278        source: common_meta::error::Error,
279    },
280
281    #[snafu(display("Failed to decompress snappy prometheus remote request"))]
282    DecompressSnappyPromRemoteRequest {
283        #[snafu(implicit)]
284        location: Location,
285        #[snafu(source)]
286        error: snap::Error,
287    },
288
289    #[snafu(display("Failed to decompress zstd prometheus remote request"))]
290    DecompressZstdPromRemoteRequest {
291        #[snafu(implicit)]
292        location: Location,
293        #[snafu(source)]
294        error: std::io::Error,
295    },
296
297    #[snafu(display("Failed to compress prometheus remote request"))]
298    CompressPromRemoteRequest {
299        #[snafu(implicit)]
300        location: Location,
301        #[snafu(source)]
302        error: snap::Error,
303    },
304
305    #[snafu(display("Invalid prometheus remote request, msg: {}", msg))]
306    InvalidPromRemoteRequest {
307        msg: String,
308        #[snafu(implicit)]
309        location: Location,
310    },
311
312    #[snafu(display("Invalid prometheus remote read query result, msg: {}", msg))]
313    InvalidPromRemoteReadQueryResult {
314        msg: String,
315        #[snafu(implicit)]
316        location: Location,
317    },
318
319    #[snafu(display("Invalid Flight ticket"))]
320    InvalidFlightTicket {
321        #[snafu(source)]
322        error: api::DecodeError,
323        #[snafu(implicit)]
324        location: Location,
325    },
326
327    #[snafu(display("Tls is required for {}, plain connection is rejected", server))]
328    TlsRequired { server: String },
329
330    #[snafu(display("Failed to get user info"))]
331    Auth {
332        #[snafu(implicit)]
333        location: Location,
334        source: auth::error::Error,
335    },
336
337    #[snafu(display("Not found http or grpc authorization header"))]
338    NotFoundAuthHeader {},
339
340    #[snafu(display("Not found influx http authorization info"))]
341    NotFoundInfluxAuth {},
342
343    #[snafu(display("Unsupported http auth scheme, name: {}", name))]
344    UnsupportedAuthScheme { name: String },
345
346    #[snafu(display("Invalid visibility ASCII chars"))]
347    InvalidAuthHeaderInvisibleASCII {
348        #[snafu(source)]
349        error: hyper::header::ToStrError,
350        #[snafu(implicit)]
351        location: Location,
352    },
353
354    #[snafu(display("Invalid utf-8 value"))]
355    InvalidAuthHeaderInvalidUtf8Value {
356        #[snafu(source)]
357        error: FromUtf8Error,
358        #[snafu(implicit)]
359        location: Location,
360    },
361
362    #[snafu(display("Invalid http authorization header"))]
363    InvalidAuthHeader {
364        #[snafu(implicit)]
365        location: Location,
366    },
367
368    #[snafu(display("Invalid base64 value"))]
369    InvalidBase64Value {
370        #[snafu(source)]
371        error: DecodeError,
372        #[snafu(implicit)]
373        location: Location,
374    },
375
376    #[snafu(display("Invalid utf-8 value"))]
377    InvalidUtf8Value {
378        #[snafu(source)]
379        error: FromUtf8Error,
380        #[snafu(implicit)]
381        location: Location,
382    },
383
384    #[snafu(display("Invalid http header value"))]
385    InvalidHeaderValue {
386        #[snafu(source)]
387        error: InvalidHeaderValue,
388        #[snafu(implicit)]
389        location: Location,
390    },
391
392    #[snafu(display("Error accessing catalog"))]
393    Catalog {
394        source: catalog::error::Error,
395        #[snafu(implicit)]
396        location: Location,
397    },
398
399    #[snafu(display("Cannot find requested table: {}.{}.{}", catalog, schema, table))]
400    TableNotFound {
401        catalog: String,
402        schema: String,
403        table: String,
404        #[snafu(implicit)]
405        location: Location,
406    },
407
408    #[cfg(feature = "mem-prof")]
409    #[snafu(display("Failed to dump profile data"))]
410    DumpProfileData {
411        #[snafu(implicit)]
412        location: Location,
413        source: common_mem_prof::error::Error,
414    },
415
416    #[snafu(display("Invalid prepare statement: {}", err_msg))]
417    InvalidPrepareStatement {
418        err_msg: String,
419        #[snafu(implicit)]
420        location: Location,
421    },
422
423    #[snafu(display("Failed to build HTTP response"))]
424    BuildHttpResponse {
425        #[snafu(source)]
426        error: http::Error,
427        #[snafu(implicit)]
428        location: Location,
429    },
430
431    #[snafu(display("Failed to parse PromQL: {query:?}"))]
432    ParsePromQL {
433        query: Box<PromQuery>,
434        #[snafu(implicit)]
435        location: Location,
436        source: query::error::Error,
437    },
438
439    #[snafu(display("Failed to parse timestamp: {}", timestamp))]
440    ParseTimestamp {
441        timestamp: String,
442        #[snafu(implicit)]
443        location: Location,
444        #[snafu(source)]
445        error: query::error::Error,
446    },
447
448    #[snafu(display("Failed to infer parameter types"))]
449    InferParameterTypes {
450        #[snafu(implicit)]
451        location: Location,
452        #[snafu(source)]
453        error: query::error::Error,
454    },
455
456    #[snafu(display("{}", reason))]
457    UnexpectedResult {
458        reason: String,
459        #[snafu(implicit)]
460        location: Location,
461    },
462
463    // this error is used for custom error mapping
464    // please do not delete it
465    #[snafu(display("Other error"))]
466    Other {
467        source: BoxedError,
468        #[snafu(implicit)]
469        location: Location,
470    },
471
472    #[snafu(display("Failed to join task"))]
473    JoinTask {
474        #[snafu(source)]
475        error: tokio::task::JoinError,
476        #[snafu(implicit)]
477        location: Location,
478    },
479
480    #[cfg(feature = "pprof")]
481    #[snafu(display("Failed to dump pprof data"))]
482    DumpPprof { source: common_pprof::error::Error },
483
484    #[cfg(not(windows))]
485    #[snafu(display("Failed to update jemalloc metrics"))]
486    UpdateJemallocMetrics {
487        #[snafu(source)]
488        error: tikv_jemalloc_ctl::Error,
489        #[snafu(implicit)]
490        location: Location,
491    },
492
493    #[snafu(display("DataFrame operation error"))]
494    DataFrame {
495        #[snafu(source)]
496        error: datafusion::error::DataFusionError,
497        #[snafu(implicit)]
498        location: Location,
499    },
500
501    #[snafu(display("Failed to convert scalar value"))]
502    ConvertScalarValue {
503        source: datatypes::error::Error,
504        #[snafu(implicit)]
505        location: Location,
506    },
507
508    #[snafu(display("Expected type: {:?}, actual: {:?}", expected, actual))]
509    PreparedStmtTypeMismatch {
510        expected: ConcreteDataType,
511        actual: opensrv_mysql::ColumnType,
512        #[snafu(implicit)]
513        location: Location,
514    },
515
516    #[snafu(display(
517        "Column: {}, {} incompatible, expected: {}, actual: {}",
518        column_name,
519        datatype,
520        expected,
521        actual
522    ))]
523    IncompatibleSchema {
524        column_name: String,
525        datatype: String,
526        expected: i32,
527        actual: i32,
528        #[snafu(implicit)]
529        location: Location,
530    },
531
532    #[snafu(display("Failed to convert to json"))]
533    ToJson {
534        #[snafu(source)]
535        error: serde_json::error::Error,
536        #[snafu(implicit)]
537        location: Location,
538    },
539
540    #[snafu(display("Failed to parse payload as json"))]
541    ParseJson {
542        #[snafu(source)]
543        error: serde_json::error::Error,
544        #[snafu(implicit)]
545        location: Location,
546    },
547
548    #[snafu(display("Invalid Loki labels: {}", msg))]
549    InvalidLokiLabels {
550        msg: String,
551        #[snafu(implicit)]
552        location: Location,
553    },
554
555    #[snafu(display("Invalid Loki JSON request: {}", msg))]
556    InvalidLokiPayload {
557        msg: String,
558        #[snafu(implicit)]
559        location: Location,
560    },
561
562    #[snafu(display("Unsupported content type: {:?}", content_type))]
563    UnsupportedContentType {
564        content_type: ContentType,
565        #[snafu(implicit)]
566        location: Location,
567    },
568
569    #[snafu(display("Failed to decode url"))]
570    UrlDecode {
571        #[snafu(source)]
572        error: FromUtf8Error,
573        #[snafu(implicit)]
574        location: Location,
575    },
576
577    #[snafu(display("Failed to convert Mysql value, error: {}", err_msg))]
578    MysqlValueConversion {
579        err_msg: String,
580        #[snafu(implicit)]
581        location: Location,
582    },
583
584    #[snafu(display("Invalid table name"))]
585    InvalidTableName {
586        #[snafu(source)]
587        error: tonic::metadata::errors::ToStrError,
588        #[snafu(implicit)]
589        location: Location,
590    },
591
592    #[snafu(display("Failed to initialize a watcher for file {}", path))]
593    FileWatch {
594        path: String,
595        #[snafu(source)]
596        error: notify::Error,
597    },
598
599    #[snafu(display("Timestamp overflow: {}", error))]
600    TimestampOverflow {
601        error: String,
602        #[snafu(implicit)]
603        location: Location,
604    },
605
606    #[snafu(display("Unsupported json data type for tag: {} {}", key, ty))]
607    UnsupportedJsonDataTypeForTag {
608        key: String,
609        ty: String,
610        #[snafu(implicit)]
611        location: Location,
612    },
613
614    #[snafu(display("Convert SQL value error"))]
615    ConvertSqlValue {
616        source: datatypes::error::Error,
617        #[snafu(implicit)]
618        location: Location,
619    },
620
621    #[snafu(display("Prepare statement not found: {}", name))]
622    PrepareStatementNotFound {
623        name: String,
624        #[snafu(implicit)]
625        location: Location,
626    },
627
628    #[snafu(display("Invalid elasticsearch input, reason: {}", reason))]
629    InvalidElasticsearchInput {
630        reason: String,
631        #[snafu(implicit)]
632        location: Location,
633    },
634
635    #[snafu(display("Invalid Jaeger query, reason: {}", reason))]
636    InvalidJaegerQuery {
637        reason: String,
638        #[snafu(implicit)]
639        location: Location,
640    },
641
642    #[snafu(display("DataFusion error"))]
643    DataFusion {
644        #[snafu(source)]
645        error: DataFusionError,
646        #[snafu(implicit)]
647        location: Location,
648    },
649
650    #[snafu(display("Failed to handle otel-arrow request, error message: {}", err_msg))]
651    HandleOtelArrowRequest {
652        err_msg: String,
653        #[snafu(implicit)]
654        location: Location,
655    },
656
657    #[snafu(display("Unknown hint: {}", hint))]
658    UnknownHint { hint: String },
659
660    #[snafu(display("Query has been cancelled"))]
661    Cancelled {
662        #[snafu(implicit)]
663        location: Location,
664    },
665
666    #[snafu(display("Service suspended"))]
667    Suspended {
668        #[snafu(implicit)]
669        location: Location,
670    },
671
672    #[snafu(transparent)]
673    GreptimeProto {
674        source: api::error::Error,
675        #[snafu(implicit)]
676        location: Location,
677    },
678}
679
680pub type Result<T, E = Error> = std::result::Result<T, E>;
681
682impl ErrorExt for Error {
683    fn status_code(&self) -> StatusCode {
684        use Error::*;
685        match self {
686            Internal { .. }
687            | InternalIo { .. }
688            | TokioIo { .. }
689            | StartHttp { .. }
690            | StartGrpc { .. }
691            | TcpBind { .. }
692            | BuildHttpResponse { .. }
693            | Arrow { .. }
694            | FileWatch { .. } => StatusCode::Internal,
695
696            AddressBind { .. }
697            | AlreadyStarted { .. }
698            | InvalidPromRemoteReadQueryResult { .. }
699            | OtlpMetricModeIncompatible { .. } => StatusCode::IllegalState,
700
701            UnsupportedDataType { .. } => StatusCode::Unsupported,
702
703            #[cfg(not(windows))]
704            UpdateJemallocMetrics { .. } => StatusCode::Internal,
705
706            CollectRecordbatch { .. } => StatusCode::EngineExecuteQuery,
707
708            ExecuteQuery { source, .. }
709            | ExecutePlan { source, .. }
710            | ExecuteGrpcQuery { source, .. }
711            | ExecuteGrpcRequest { source, .. }
712            | CheckDatabaseValidity { source, .. } => source.status_code(),
713
714            Pipeline { source, .. } => source.status_code(),
715            CommonMeta { source, .. } => source.status_code(),
716
717            NotSupported { .. }
718            | InvalidParameter { .. }
719            | InvalidQuery { .. }
720            | InfluxdbLineProtocol { .. }
721            | InvalidOpentsdbJsonRequest { .. }
722            | DecodePromRemoteRequest { .. }
723            | DecodeOtlpRequest { .. }
724            | DecodeLokiRequest { .. }
725            | UnsupportedJsonContentType { .. }
726            | CompressPromRemoteRequest { .. }
727            | DecompressSnappyPromRemoteRequest { .. }
728            | DecompressZstdPromRemoteRequest { .. }
729            | InvalidPromRemoteRequest { .. }
730            | InvalidFlightTicket { .. }
731            | InvalidPrepareStatement { .. }
732            | InferParameterTypes { .. }
733            | DataFrame { .. }
734            | PreparedStmtTypeMismatch { .. }
735            | TimePrecision { .. }
736            | UrlDecode { .. }
737            | IncompatibleSchema { .. }
738            | MysqlValueConversion { .. }
739            | ParseJson { .. }
740            | InvalidLokiLabels { .. }
741            | InvalidLokiPayload { .. }
742            | UnsupportedContentType { .. }
743            | TimestampOverflow { .. }
744            | UnsupportedJsonDataTypeForTag { .. }
745            | InvalidTableName { .. }
746            | PrepareStatementNotFound { .. }
747            | FailedToParseQuery { .. }
748            | InvalidElasticsearchInput { .. }
749            | InvalidJaegerQuery { .. }
750            | ParseTimestamp { .. }
751            | UnknownHint { .. } => StatusCode::InvalidArguments,
752
753            Catalog { source, .. } => source.status_code(),
754            RowWriter { source, .. } => source.status_code(),
755
756            TlsRequired { .. } => StatusCode::Unknown,
757            Auth { source, .. } => source.status_code(),
758            DescribeStatement { source } => source.status_code(),
759
760            NotFoundAuthHeader { .. } | NotFoundInfluxAuth { .. } => StatusCode::AuthHeaderNotFound,
761            InvalidAuthHeaderInvisibleASCII { .. }
762            | UnsupportedAuthScheme { .. }
763            | InvalidAuthHeader { .. }
764            | InvalidBase64Value { .. }
765            | InvalidAuthHeaderInvalidUtf8Value { .. } => StatusCode::InvalidAuthHeader,
766
767            TableNotFound { .. } => StatusCode::TableNotFound,
768
769            #[cfg(feature = "mem-prof")]
770            DumpProfileData { source, .. } => source.status_code(),
771
772            InvalidUtf8Value { .. } | InvalidHeaderValue { .. } => StatusCode::InvalidArguments,
773
774            TooManyConcurrentRequests { .. } => StatusCode::RuntimeResourcesExhausted,
775
776            ParsePromQL { source, .. } => source.status_code(),
777            Other { source, .. } => source.status_code(),
778
779            UnexpectedResult { .. } => StatusCode::Unexpected,
780
781            JoinTask { error, .. } => {
782                if error.is_cancelled() {
783                    StatusCode::Cancelled
784                } else if error.is_panic() {
785                    StatusCode::Unexpected
786                } else {
787                    StatusCode::Unknown
788                }
789            }
790
791            #[cfg(feature = "pprof")]
792            DumpPprof { source, .. } => source.status_code(),
793
794            ConvertScalarValue { source, .. } => source.status_code(),
795
796            ToJson { .. } | DataFusion { .. } => StatusCode::Internal,
797
798            ConvertSqlValue { source, .. } => source.status_code(),
799
800            HandleOtelArrowRequest { .. } => StatusCode::Internal,
801
802            Cancelled { .. } => StatusCode::Cancelled,
803
804            Suspended { .. } => StatusCode::Suspended,
805
806            MemoryLimitExceeded { .. } => StatusCode::RateLimited,
807
808            GreptimeProto { source, .. } => source.status_code(),
809        }
810    }
811
812    fn as_any(&self) -> &dyn Any {
813        self
814    }
815}
816
817define_into_tonic_status!(Error);
818
819impl From<std::io::Error> for Error {
820    fn from(e: std::io::Error) -> Self {
821        Error::InternalIo { error: e }
822    }
823}
824
825fn log_error_if_necessary(error: &Error) {
826    if error.status_code().should_log_error() {
827        error!(error; "Failed to handle HTTP request ");
828    } else {
829        warn!(error; "Failed to handle HTTP request ");
830    }
831}
832
833impl IntoResponse for Error {
834    fn into_response(self) -> Response {
835        let error_msg = self.output_msg();
836        let status = status_code_to_http_status(&self.status_code());
837
838        log_error_if_necessary(&self);
839
840        let body = Json(json!({
841            "error": error_msg,
842        }));
843        (status, body).into_response()
844    }
845}
846
847/// Converts [StatusCode] to [HttpStatusCode].
848pub fn status_code_to_http_status(status_code: &StatusCode) -> HttpStatusCode {
849    match status_code {
850        StatusCode::Success => HttpStatusCode::OK,
851
852        // When a request is cancelled by the client (e.g., by a client side timeout),
853        // we should return a gateway timeout status code to the external client.
854        StatusCode::Cancelled | StatusCode::DeadlineExceeded => HttpStatusCode::GATEWAY_TIMEOUT,
855
856        StatusCode::Unsupported
857        | StatusCode::InvalidArguments
858        | StatusCode::InvalidSyntax
859        | StatusCode::RequestOutdated
860        | StatusCode::RegionAlreadyExists
861        | StatusCode::TableColumnExists
862        | StatusCode::TableAlreadyExists
863        | StatusCode::RegionNotFound
864        | StatusCode::DatabaseNotFound
865        | StatusCode::TableNotFound
866        | StatusCode::TableColumnNotFound
867        | StatusCode::PlanQuery
868        | StatusCode::DatabaseAlreadyExists
869        | StatusCode::TriggerAlreadyExists
870        | StatusCode::TriggerNotFound
871        | StatusCode::FlowNotFound
872        | StatusCode::FlowAlreadyExists => HttpStatusCode::BAD_REQUEST,
873
874        StatusCode::AuthHeaderNotFound
875        | StatusCode::InvalidAuthHeader
876        | StatusCode::UserNotFound
877        | StatusCode::UnsupportedPasswordType
878        | StatusCode::UserPasswordMismatch
879        | StatusCode::RegionReadonly => HttpStatusCode::UNAUTHORIZED,
880
881        StatusCode::PermissionDenied | StatusCode::AccessDenied => HttpStatusCode::FORBIDDEN,
882
883        StatusCode::RateLimited => HttpStatusCode::TOO_MANY_REQUESTS,
884
885        StatusCode::RegionNotReady
886        | StatusCode::TableUnavailable
887        | StatusCode::RegionBusy
888        | StatusCode::StorageUnavailable
889        | StatusCode::External
890        | StatusCode::Suspended => HttpStatusCode::SERVICE_UNAVAILABLE,
891
892        StatusCode::Internal
893        | StatusCode::Unexpected
894        | StatusCode::IllegalState
895        | StatusCode::Unknown
896        | StatusCode::RuntimeResourcesExhausted
897        | StatusCode::EngineExecuteQuery => HttpStatusCode::INTERNAL_SERVER_ERROR,
898    }
899}