Skip to main content

grpcurl_core/
error.rs

1use std::fmt;
2
3/// All error types produced by the grpcurl-rs library.
4///
5/// Maps to the Go codebase's error types:
6/// - `notFoundError` (invoke.go:382)
7/// - `ErrReflectionNotSupported` (desc_source.go)
8/// - Various ad-hoc errors wrapped in `fmt.Errorf`
9#[derive(Debug)]
10pub enum GrpcurlError {
11    /// The requested symbol (service, method, message, etc.) was not found.
12    /// Equivalent to Go's `notFoundError` and `grpcreflect.IsElementNotFoundError`.
13    NotFound(String),
14
15    /// The server does not support the gRPC reflection API.
16    /// Equivalent to Go's `ErrReflectionNotSupported`.
17    ReflectionNotSupported,
18
19    /// An invalid argument was provided (e.g., malformed method name).
20    InvalidArgument(String),
21
22    /// An I/O error (file read, network, etc.).
23    Io(std::io::Error),
24
25    /// A protobuf encoding/decoding error.
26    Proto(String),
27
28    /// A gRPC status error from the server.
29    GrpcStatus(tonic::Status),
30
31    /// Any other error.
32    Other(Box<dyn std::error::Error + Send + Sync>),
33}
34
35impl fmt::Display for GrpcurlError {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            GrpcurlError::NotFound(name) => write!(f, "Symbol not found: {name}"),
39            GrpcurlError::ReflectionNotSupported => {
40                write!(f, "server does not support the reflection API")
41            }
42            GrpcurlError::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"),
43            GrpcurlError::Io(err) => write!(f, "I/O error: {err}"),
44            GrpcurlError::Proto(msg) => write!(f, "proto error: {msg}"),
45            GrpcurlError::GrpcStatus(status) => {
46                write!(f, "gRPC error: {} - {}", status.code(), status.message())
47            }
48            GrpcurlError::Other(err) => write!(f, "{err}"),
49        }
50    }
51}
52
53impl std::error::Error for GrpcurlError {
54    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55        match self {
56            GrpcurlError::Io(err) => Some(err),
57            GrpcurlError::Other(err) => Some(err.as_ref()),
58            _ => None,
59        }
60    }
61}
62
63impl From<std::io::Error> for GrpcurlError {
64    fn from(err: std::io::Error) -> Self {
65        GrpcurlError::Io(err)
66    }
67}
68
69impl From<tonic::Status> for GrpcurlError {
70    fn from(status: tonic::Status) -> Self {
71        GrpcurlError::GrpcStatus(status)
72    }
73}
74
75/// Convenience type alias used throughout the codebase.
76pub type Result<T> = std::result::Result<T, GrpcurlError>;
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    /// Check whether an error represents a "not found" condition.
83    ///
84    /// Equivalent to Go's `isNotFoundError()` which checks for both the local
85    /// `notFoundError` type and `grpcreflect.IsElementNotFoundError()`.
86    fn is_not_found_error(err: &GrpcurlError) -> bool {
87        match err {
88            GrpcurlError::NotFound(_) => true,
89            GrpcurlError::GrpcStatus(status) => status.code() == tonic::Code::NotFound,
90            _ => false,
91        }
92    }
93
94    #[test]
95    fn not_found_error_detected() {
96        let err = GrpcurlError::NotFound("my.Service".into());
97        assert!(is_not_found_error(&err));
98    }
99
100    #[test]
101    fn grpc_not_found_detected() {
102        let status = tonic::Status::not_found("service not found");
103        let err = GrpcurlError::GrpcStatus(status);
104        assert!(is_not_found_error(&err));
105    }
106
107    #[test]
108    fn other_errors_not_detected_as_not_found() {
109        let err = GrpcurlError::InvalidArgument("bad input".into());
110        assert!(!is_not_found_error(&err));
111
112        let err = GrpcurlError::ReflectionNotSupported;
113        assert!(!is_not_found_error(&err));
114    }
115
116    #[test]
117    fn display_formatting() {
118        let err = GrpcurlError::NotFound("my.Service".into());
119        assert_eq!(err.to_string(), "Symbol not found: my.Service");
120
121        let err = GrpcurlError::ReflectionNotSupported;
122        assert_eq!(
123            err.to_string(),
124            "server does not support the reflection API"
125        );
126    }
127
128    #[test]
129    fn io_error_conversion() {
130        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
131        let err: GrpcurlError = io_err.into();
132        assert!(matches!(err, GrpcurlError::Io(_)));
133    }
134}