Skip to main content

force_pubsub/
error.rs

1//! Error types for the force-pubsub crate.
2//!
3//! All errors originating from Pub/Sub API operations are represented by
4//! [`PubSubError`]. A convenience [`Result`] alias is provided for use
5//! throughout the crate.
6
7use thiserror::Error;
8
9/// All errors from the force-pubsub crate.
10#[derive(Debug, Error)]
11pub enum PubSubError {
12    /// gRPC transport or protocol error.
13    #[error("gRPC transport error: {0}")]
14    Transport(#[from] tonic::Status),
15
16    /// Avro encoding or decoding failure.
17    #[error("Avro error: {0}")]
18    Avro(String),
19
20    /// Schema not found for the given ID.
21    #[error("schema not found: {schema_id}")]
22    SchemaNotFound {
23        /// The schema ID that could not be resolved.
24        schema_id: String,
25    },
26
27    /// Authentication or token error from the force crate.
28    #[error("auth error: {0}")]
29    Auth(#[from] force::error::ForceError),
30
31    /// Reconnection to the subscribe stream was exhausted.
32    #[error("reconnect failed after {attempts} attempt(s): {last_error}")]
33    ReconnectFailed {
34        /// Number of reconnection attempts made.
35        attempts: u32,
36        /// The error returned by the last attempt.
37        last_error: Box<Self>,
38    },
39
40    /// gRPC channel setup failed.
41    #[error("failed to connect to Pub/Sub endpoint: {0}")]
42    Connect(#[from] tonic::transport::Error),
43
44    /// Invalid configuration.
45    #[error("invalid configuration: {0}")]
46    Config(String),
47}
48
49/// Convenience Result alias for Pub/Sub operations.
50pub type Result<T> = std::result::Result<T, PubSubError>;
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_schema_not_found_display() {
58        let err = PubSubError::SchemaNotFound {
59            schema_id: "abc123".to_string(),
60        };
61        assert_eq!(err.to_string(), "schema not found: abc123");
62    }
63
64    #[test]
65    fn test_config_error_display() {
66        let err = PubSubError::Config("batch_size must be > 0".to_string());
67        assert_eq!(
68            err.to_string(),
69            "invalid configuration: batch_size must be > 0"
70        );
71    }
72
73    #[test]
74    fn test_avro_error_display() {
75        let err = PubSubError::Avro("unexpected end of buffer".to_string());
76        assert_eq!(err.to_string(), "Avro error: unexpected end of buffer");
77    }
78
79    #[test]
80    fn test_reconnect_failed_display() {
81        let inner = Box::new(PubSubError::Config("test".to_string()));
82        let err = PubSubError::ReconnectFailed {
83            attempts: 3,
84            last_error: inner,
85        };
86        assert!(err.to_string().contains("3 attempt(s)"));
87    }
88
89    #[test]
90    fn test_from_tonic_status() {
91        let status = tonic::Status::not_found("topic not found");
92        let err = PubSubError::from(status);
93        assert!(matches!(err, PubSubError::Transport(_)));
94    }
95}