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(transparent)]
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    #[snafu(transparent)]
683    DataTypes {
684        source: datatypes::error::Error,
685        #[snafu(implicit)]
686        location: Location,
687    },
688}
689
690pub type Result<T, E = Error> = std::result::Result<T, E>;
691
692impl ErrorExt for Error {
693    fn status_code(&self) -> StatusCode {
694        use Error::*;
695        match self {
696            Internal { .. }
697            | BatcherChannelClosed
698            | InternalIo { .. }
699            | TokioIo { .. }
700            | StartHttp { .. }
701            | StartGrpc { .. }
702            | TcpBind { .. }
703            | BuildHttpResponse { .. }
704            | Arrow { .. }
705            | FileWatch { .. } => StatusCode::Internal,
706
707            AddressBind { .. }
708            | AlreadyStarted { .. }
709            | InvalidPromRemoteReadQueryResult { .. }
710            | OtlpMetricModeIncompatible { .. } => StatusCode::IllegalState,
711
712            UnsupportedDataType { .. } => StatusCode::Unsupported,
713
714            #[cfg(not(windows))]
715            UpdateJemallocMetrics { .. } => StatusCode::Internal,
716
717            CollectRecordbatch { .. } => StatusCode::EngineExecuteQuery,
718
719            ExecuteQuery { source, .. }
720            | ExecutePlan { source, .. }
721            | ExecuteGrpcQuery { source, .. }
722            | ExecuteGrpcRequest { source, .. }
723            | CheckDatabaseValidity { source, .. } => source.status_code(),
724
725            Pipeline { source, .. } => source.status_code(),
726            CommonMeta { source, .. } => source.status_code(),
727
728            NotSupported { .. }
729            | InvalidParameter { .. }
730            | InvalidQuery { .. }
731            | InfluxdbLineProtocol { .. }
732            | InvalidOpentsdbJsonRequest { .. }
733            | DecodePromRemoteRequest { .. }
734            | DecodeOtlpRequest { .. }
735            | DecodeLokiRequest { .. }
736            | UnsupportedJsonContentType { .. }
737            | CompressPromRemoteRequest { .. }
738            | DecompressSnappyPromRemoteRequest { .. }
739            | DecompressZstdPromRemoteRequest { .. }
740            | InvalidPromRemoteRequest { .. }
741            | InvalidFlightTicket { .. }
742            | InvalidPrepareStatement { .. }
743            | InferParameterTypes { .. }
744            | DataFrame { .. }
745            | PreparedStmtTypeMismatch { .. }
746            | TimePrecision { .. }
747            | UrlDecode { .. }
748            | IncompatibleSchema { .. }
749            | MysqlValueConversion { .. }
750            | ParseJson { .. }
751            | InvalidLokiLabels { .. }
752            | InvalidLokiPayload { .. }
753            | UnsupportedContentType { .. }
754            | TimestampOverflow { .. }
755            | UnsupportedJsonDataTypeForTag { .. }
756            | InvalidTableName { .. }
757            | PrepareStatementNotFound { .. }
758            | FailedToParseQuery { .. }
759            | InvalidElasticsearchInput { .. }
760            | InvalidJaegerQuery { .. }
761            | ParseTimestamp { .. }
762            | UnknownHint { .. } => StatusCode::InvalidArguments,
763
764            Catalog { source, .. } => source.status_code(),
765            RowWriter { source, .. } => source.status_code(),
766            DataTypes { source, .. } => source.status_code(),
767
768            TlsRequired { .. } => StatusCode::Unknown,
769            Auth { source, .. } => source.status_code(),
770            DescribeStatement { source } => source.status_code(),
771
772            NotFoundAuthHeader { .. } | NotFoundInfluxAuth { .. } => StatusCode::AuthHeaderNotFound,
773            InvalidAuthHeaderInvisibleASCII { .. }
774            | UnsupportedAuthScheme { .. }
775            | InvalidAuthHeader { .. }
776            | InvalidBase64Value { .. }
777            | InvalidAuthHeaderInvalidUtf8Value { .. } => StatusCode::InvalidAuthHeader,
778
779            TableNotFound { .. } => StatusCode::TableNotFound,
780
781            #[cfg(feature = "mem-prof")]
782            DumpProfileData { source, .. } => source.status_code(),
783
784            InvalidUtf8Value { .. } | InvalidHeaderValue { .. } => StatusCode::InvalidArguments,
785
786            TooManyConcurrentRequests { .. } => StatusCode::RuntimeResourcesExhausted,
787
788            ParsePromQL { source, .. } => source.status_code(),
789            Other { source, .. } => source.status_code(),
790
791            UnexpectedResult { .. } => StatusCode::Unexpected,
792
793            JoinTask { error, .. } => {
794                if error.is_cancelled() {
795                    StatusCode::Cancelled
796                } else if error.is_panic() {
797                    StatusCode::Unexpected
798                } else {
799                    StatusCode::Unknown
800                }
801            }
802
803            #[cfg(feature = "pprof")]
804            DumpPprof { source, .. } => source.status_code(),
805
806            ConvertScalarValue { source, .. } => source.status_code(),
807
808            ToJson { .. } | DataFusion { .. } => StatusCode::Internal,
809
810            ConvertSqlValue { source, .. } => source.status_code(),
811
812            HandleOtelArrowRequest { .. } => StatusCode::Internal,
813
814            Cancelled { .. } => StatusCode::Cancelled,
815
816            Suspended { .. } => StatusCode::Suspended,
817
818            MemoryLimitExceeded { .. } => StatusCode::RateLimited,
819
820            GreptimeProto { source, .. } => source.status_code(),
821        }
822    }
823
824    fn as_any(&self) -> &dyn Any {
825        self
826    }
827}
828
829define_into_tonic_status!(Error);
830
831impl From<std::io::Error> for Error {
832    fn from(e: std::io::Error) -> Self {
833        Error::InternalIo { error: e }
834    }
835}
836
837fn log_error_if_necessary(error: &Error) {
838    if error.status_code().should_log_error() {
839        error!(error; "Failed to handle HTTP request ");
840    } else {
841        warn!(error; "Failed to handle HTTP request ");
842    }
843}
844
845impl IntoResponse for Error {
846    fn into_response(self) -> Response {
847        let error_msg = self.output_msg();
848        let status = status_code_to_http_status(&self.status_code());
849
850        log_error_if_necessary(&self);
851
852        let body = Json(json!({
853            "error": error_msg,
854        }));
855        (status, body).into_response()
856    }
857}
858
859/// Converts [StatusCode] to [HttpStatusCode].
860pub fn status_code_to_http_status(status_code: &StatusCode) -> HttpStatusCode {
861    match status_code {
862        StatusCode::Success => HttpStatusCode::OK,
863
864        // When a request is cancelled by the client (e.g., by a client side timeout),
865        // we should return a gateway timeout status code to the external client.
866        StatusCode::Cancelled | StatusCode::DeadlineExceeded => HttpStatusCode::GATEWAY_TIMEOUT,
867
868        StatusCode::Unsupported
869        | StatusCode::InvalidArguments
870        | StatusCode::InvalidSyntax
871        | StatusCode::RequestOutdated
872        | StatusCode::RegionAlreadyExists
873        | StatusCode::TableColumnExists
874        | StatusCode::TableAlreadyExists
875        | StatusCode::RegionNotFound
876        | StatusCode::DatabaseNotFound
877        | StatusCode::TableNotFound
878        | StatusCode::TableColumnNotFound
879        | StatusCode::PlanQuery
880        | StatusCode::DatabaseAlreadyExists
881        | StatusCode::TriggerAlreadyExists
882        | StatusCode::TriggerNotFound
883        | StatusCode::FlowNotFound
884        | StatusCode::FlowAlreadyExists => HttpStatusCode::BAD_REQUEST,
885
886        StatusCode::AuthHeaderNotFound
887        | StatusCode::InvalidAuthHeader
888        | StatusCode::UserNotFound
889        | StatusCode::UnsupportedPasswordType
890        | StatusCode::UserPasswordMismatch
891        | StatusCode::RegionReadonly => HttpStatusCode::UNAUTHORIZED,
892
893        StatusCode::PermissionDenied | StatusCode::AccessDenied => HttpStatusCode::FORBIDDEN,
894
895        StatusCode::RateLimited => HttpStatusCode::TOO_MANY_REQUESTS,
896
897        StatusCode::RegionNotReady
898        | StatusCode::TableUnavailable
899        | StatusCode::RegionBusy
900        | StatusCode::StorageUnavailable
901        | StatusCode::External
902        | StatusCode::Suspended => HttpStatusCode::SERVICE_UNAVAILABLE,
903
904        StatusCode::Internal
905        | StatusCode::Unexpected
906        | StatusCode::IllegalState
907        | StatusCode::Unknown
908        | StatusCode::RuntimeResourcesExhausted
909        | StatusCode::EngineExecuteQuery => HttpStatusCode::INTERNAL_SERVER_ERROR,
910    }
911}