Skip to main content

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