1use std::any::Any;
16
17use common_error::define_from_tonic_status;
18use common_error::ext::{BoxedError, ErrorExt, RetryHint};
19use common_error::status_code::StatusCode;
20use common_macro::stack_trace_debug;
21use snafu::{Location, Snafu};
22use tonic::Code;
23use tonic::metadata::errors::InvalidMetadataValue;
24
25#[derive(Snafu)]
26#[snafu(visibility(pub))]
27#[stack_trace_debug]
28pub enum Error {
29 #[snafu(display("Illegal Flight messages, reason: {}", reason))]
30 IllegalFlightMessages {
31 reason: String,
32 #[snafu(implicit)]
33 location: Location,
34 },
35
36 #[snafu(display("Failed to do Flight get, code: {}", tonic_code))]
37 FlightGet {
38 addr: String,
39 tonic_code: Code,
40 source: BoxedError,
41 },
42
43 #[snafu(display(
44 "Failed to receive Flight data from {}, code: {}: {}",
45 addr,
46 tonic_code,
47 message
48 ))]
49 FlightStream {
50 addr: String,
51 tonic_code: Code,
52 message: String,
53 source: BoxedError,
54 #[snafu(implicit)]
55 location: Location,
56 },
57
58 #[snafu(display("Failed to convert FlightData"))]
59 ConvertFlightData {
60 #[snafu(implicit)]
61 location: Location,
62 source: common_grpc::Error,
63 },
64
65 #[snafu(display("Illegal GRPC client state: {}", err_msg))]
66 IllegalGrpcClientState {
67 err_msg: String,
68 #[snafu(implicit)]
69 location: Location,
70 },
71
72 #[snafu(display("Missing required field in protobuf, field: {}", field))]
73 MissingField {
74 field: String,
75 #[snafu(implicit)]
76 location: Location,
77 },
78
79 #[snafu(display("Failed to create gRPC channel, peer address: {}", addr))]
80 CreateChannel {
81 addr: String,
82 #[snafu(implicit)]
83 location: Location,
84 source: common_grpc::error::Error,
85 },
86
87 #[snafu(display("Failed to create Tls channel manager"))]
88 CreateTlsChannel {
89 #[snafu(implicit)]
90 location: Location,
91 source: common_grpc::error::Error,
92 },
93
94 #[snafu(display("Failed to request RegionServer {}, code: {}", addr, code))]
95 RegionServer {
96 addr: String,
97 code: Code,
98 source: BoxedError,
99 #[snafu(implicit)]
100 location: Location,
101 },
102
103 #[snafu(display("Failed to request FlowServer {}, code: {}", addr, code))]
104 FlowServer {
105 addr: String,
106 code: Code,
107 source: BoxedError,
108 #[snafu(implicit)]
109 location: Location,
110 },
111
112 #[snafu(display("{}", msg))]
114 Server {
115 code: StatusCode,
116 msg: String,
117 #[snafu(implicit)]
118 location: Location,
119 },
120
121 #[snafu(display("Illegal Database response: {err_msg}"))]
122 IllegalDatabaseResponse {
123 err_msg: String,
124 #[snafu(implicit)]
125 location: Location,
126 },
127
128 #[snafu(display("Invalid Tonic metadata value"))]
129 InvalidTonicMetadataValue {
130 #[snafu(source)]
131 error: InvalidMetadataValue,
132 #[snafu(implicit)]
133 location: Location,
134 },
135
136 #[snafu(display("Failed to convert Schema"))]
137 ConvertSchema {
138 #[snafu(implicit)]
139 location: Location,
140 source: datatypes::error::Error,
141 },
142
143 #[snafu(display("{}", msg))]
144 Tonic {
145 code: StatusCode,
146 msg: String,
147 tonic_code: Code,
148 retry_hint: RetryHint,
149 #[snafu(implicit)]
150 location: Location,
151 },
152
153 #[snafu(display("External error"))]
154 External {
155 #[snafu(implicit)]
156 location: Location,
157 source: BoxedError,
158 },
159}
160
161pub type Result<T> = std::result::Result<T, Error>;
162
163impl ErrorExt for Error {
164 fn status_code(&self) -> StatusCode {
165 match self {
166 Error::IllegalFlightMessages { .. }
167 | Error::MissingField { .. }
168 | Error::IllegalDatabaseResponse { .. } => StatusCode::Internal,
169
170 Error::Server { code, .. } | Error::Tonic { code, .. } => *code,
171 Error::FlightGet { source, .. }
172 | Error::FlightStream { source, .. }
173 | Error::RegionServer { source, .. }
174 | Error::FlowServer { source, .. } => source.status_code(),
175 Error::CreateChannel { source, .. }
176 | Error::ConvertFlightData { source, .. }
177 | Error::CreateTlsChannel { source, .. } => source.status_code(),
178 Error::IllegalGrpcClientState { .. } => StatusCode::Unexpected,
179 Error::InvalidTonicMetadataValue { .. } => StatusCode::InvalidArguments,
180 Error::ConvertSchema { source, .. } => source.status_code(),
181 Error::External { source, .. } => source.status_code(),
182 }
183 }
184
185 fn as_any(&self) -> &dyn Any {
186 self
187 }
188
189 fn retry_hint(&self) -> RetryHint {
190 match self {
191 Error::Tonic { retry_hint, .. } => *retry_hint,
192 Error::FlightGet { source, .. }
193 | Error::FlightStream { source, .. }
194 | Error::RegionServer { source, .. }
195 | Error::FlowServer { source, .. }
196 | Error::External { source, .. } => source.retry_hint(),
197 Error::ConvertFlightData { source, .. }
198 | Error::CreateChannel { source, .. }
199 | Error::CreateTlsChannel { source, .. } => source.retry_hint(),
200 Error::ConvertSchema { source, .. } => source.retry_hint(),
201 _ => RetryHint::NonRetryable,
202 }
203 }
204}
205
206define_from_tonic_status!(Error, Tonic);
207
208impl Error {
209 pub fn tonic_code(&self) -> Option<Code> {
211 match self {
212 Self::FlightGet { tonic_code, .. }
213 | Self::FlightStream { tonic_code, .. }
214 | Self::RegionServer {
215 code: tonic_code, ..
216 }
217 | Self::FlowServer {
218 code: tonic_code, ..
219 }
220 | Self::Tonic { tonic_code, .. } => Some(*tonic_code),
221 _ => None,
222 }
223 }
224
225 pub fn is_connection_error(&self) -> bool {
227 matches!(self.tonic_code(), Some(Code::Unavailable))
228 }
229
230 pub fn should_retry(&self) -> bool {
231 self.retry_hint().is_retryable()
232 || self.is_connection_error()
233 || matches!(
234 self.tonic_code(),
235 Some(Code::Cancelled) | Some(Code::DeadlineExceeded)
236 )
237 }
238}