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