arrow_flight/
error.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::error::Error;
19
20use arrow_schema::ArrowError;
21
22/// Errors for the Apache Arrow Flight crate
23#[derive(Debug)]
24pub enum FlightError {
25    /// Underlying arrow error
26    Arrow(ArrowError),
27    /// Returned when functionality is not yet available.
28    NotYetImplemented(String),
29    /// Error from the underlying tonic library
30    Tonic(tonic::Status),
31    /// Some unexpected message was received
32    ProtocolError(String),
33    /// An error occurred during decoding
34    DecodeError(String),
35    /// External error that can provide source of error by calling `Error::source`.
36    ExternalError(Box<dyn Error + Send + Sync>),
37}
38
39impl FlightError {
40    /// Generate a new `FlightError::ProtocolError` variant.
41    pub fn protocol(message: impl Into<String>) -> Self {
42        Self::ProtocolError(message.into())
43    }
44
45    /// Wraps an external error in an `ArrowError`.
46    pub fn from_external_error(error: Box<dyn Error + Send + Sync>) -> Self {
47        Self::ExternalError(error)
48    }
49}
50
51impl std::fmt::Display for FlightError {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            FlightError::Arrow(source) => write!(f, "Arrow error: {}", source),
55            FlightError::NotYetImplemented(desc) => write!(f, "Not yet implemented: {}", desc),
56            FlightError::Tonic(source) => write!(f, "Tonic error: {}", source),
57            FlightError::ProtocolError(desc) => write!(f, "Protocol error: {}", desc),
58            FlightError::DecodeError(desc) => write!(f, "Decode error: {}", desc),
59            FlightError::ExternalError(source) => write!(f, "External error: {}", source),
60        }
61    }
62}
63
64impl Error for FlightError {
65    fn source(&self) -> Option<&(dyn Error + 'static)> {
66        match self {
67            FlightError::Arrow(source) => Some(source),
68            FlightError::Tonic(source) => Some(source),
69            FlightError::ExternalError(source) => Some(source.as_ref()),
70            _ => None,
71        }
72    }
73}
74
75impl From<tonic::Status> for FlightError {
76    fn from(status: tonic::Status) -> Self {
77        Self::Tonic(status)
78    }
79}
80
81impl From<ArrowError> for FlightError {
82    fn from(value: ArrowError) -> Self {
83        Self::Arrow(value)
84    }
85}
86
87// default conversion from FlightError to tonic treats everything
88// other than `Status` as an internal error
89impl From<FlightError> for tonic::Status {
90    fn from(value: FlightError) -> Self {
91        match value {
92            FlightError::Arrow(e) => tonic::Status::internal(e.to_string()),
93            FlightError::NotYetImplemented(e) => tonic::Status::internal(e),
94            FlightError::Tonic(status) => status,
95            FlightError::ProtocolError(e) => tonic::Status::internal(e),
96            FlightError::DecodeError(e) => tonic::Status::internal(e),
97            FlightError::ExternalError(e) => tonic::Status::internal(e.to_string()),
98        }
99    }
100}
101
102/// Result type for the Apache Arrow Flight crate
103pub type Result<T> = std::result::Result<T, FlightError>;
104
105#[cfg(test)]
106mod test {
107    use super::*;
108
109    #[test]
110    fn error_source() {
111        let e1 = FlightError::DecodeError("foo".into());
112        assert!(e1.source().is_none());
113
114        // one level of wrapping
115        let e2 = FlightError::ExternalError(Box::new(e1));
116        let source = e2.source().unwrap().downcast_ref::<FlightError>().unwrap();
117        assert!(matches!(source, FlightError::DecodeError(_)));
118
119        let e3 = FlightError::ExternalError(Box::new(e2));
120        let source = e3
121            .source()
122            .unwrap()
123            .downcast_ref::<FlightError>()
124            .unwrap()
125            .source()
126            .unwrap()
127            .downcast_ref::<FlightError>()
128            .unwrap();
129
130        assert!(matches!(source, FlightError::DecodeError(_)));
131    }
132
133    #[test]
134    fn error_through_arrow() {
135        // flight error that wraps an arrow error that wraps a flight error
136        let e1 = FlightError::DecodeError("foo".into());
137        let e2 = ArrowError::ExternalError(Box::new(e1));
138        let e3 = FlightError::ExternalError(Box::new(e2));
139
140        // ensure we can find the lowest level error by following source()
141        let mut root_error: &dyn Error = &e3;
142        while let Some(source) = root_error.source() {
143            // walk the next level
144            root_error = source;
145        }
146
147        let source = root_error.downcast_ref::<FlightError>().unwrap();
148        assert!(matches!(source, FlightError::DecodeError(_)));
149    }
150}