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(Box<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(Box::new(status))
78    }
79}
80
81impl From<prost::DecodeError> for FlightError {
82    fn from(error: prost::DecodeError) -> Self {
83        Self::DecodeError(error.to_string())
84    }
85}
86
87impl From<ArrowError> for FlightError {
88    fn from(value: ArrowError) -> Self {
89        Self::Arrow(value)
90    }
91}
92
93// default conversion from FlightError to tonic treats everything
94// other than `Status` as an internal error
95impl From<FlightError> for tonic::Status {
96    fn from(value: FlightError) -> Self {
97        match value {
98            FlightError::Arrow(e) => tonic::Status::internal(e.to_string()),
99            FlightError::NotYetImplemented(e) => tonic::Status::internal(e),
100            FlightError::Tonic(status) => *status,
101            FlightError::ProtocolError(e) => tonic::Status::internal(e),
102            FlightError::DecodeError(e) => tonic::Status::internal(e),
103            FlightError::ExternalError(e) => tonic::Status::internal(e.to_string()),
104        }
105    }
106}
107
108/// Result type for the Apache Arrow Flight crate
109pub type Result<T> = std::result::Result<T, FlightError>;
110
111#[cfg(test)]
112mod test {
113    use super::*;
114
115    #[test]
116    fn error_source() {
117        let e1 = FlightError::DecodeError("foo".into());
118        assert!(e1.source().is_none());
119
120        // one level of wrapping
121        let e2 = FlightError::ExternalError(Box::new(e1));
122        let source = e2.source().unwrap().downcast_ref::<FlightError>().unwrap();
123        assert!(matches!(source, FlightError::DecodeError(_)));
124
125        let e3 = FlightError::ExternalError(Box::new(e2));
126        let source = e3
127            .source()
128            .unwrap()
129            .downcast_ref::<FlightError>()
130            .unwrap()
131            .source()
132            .unwrap()
133            .downcast_ref::<FlightError>()
134            .unwrap();
135
136        assert!(matches!(source, FlightError::DecodeError(_)));
137    }
138
139    #[test]
140    fn error_through_arrow() {
141        // flight error that wraps an arrow error that wraps a flight error
142        let e1 = FlightError::DecodeError("foo".into());
143        let e2 = ArrowError::ExternalError(Box::new(e1));
144        let e3 = FlightError::ExternalError(Box::new(e2));
145
146        // ensure we can find the lowest level error by following source()
147        let mut root_error: &dyn Error = &e3;
148        while let Some(source) = root_error.source() {
149            // walk the next level
150            root_error = source;
151        }
152
153        let source = root_error.downcast_ref::<FlightError>().unwrap();
154        assert!(matches!(source, FlightError::DecodeError(_)));
155    }
156
157    #[test]
158    fn test_error_size() {
159        // use Box in variants to keep this size down
160        assert_eq!(std::mem::size_of::<FlightError>(), 32);
161    }
162}