Skip to main content

entity_core/
stream.rs

1// SPDX-FileCopyrightText: 2025-2026 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4//! Streaming types for real-time entity updates.
5//!
6//! Provides error types and traits for async streams via Postgres
7//! LISTEN/NOTIFY.
8
9use std::fmt;
10
11/// Error type for stream operations.
12#[derive(Debug)]
13pub enum StreamError<D> {
14    /// Database/listener error.
15    Database(D),
16    /// JSON deserialization error.
17    Deserialize(String)
18}
19
20impl<D> StreamError<D> {
21    /// Check if this is a database error.
22    pub const fn is_database(&self) -> bool {
23        matches!(self, Self::Database(_))
24    }
25
26    /// Check if this is a deserialization error.
27    pub const fn is_deserialize(&self) -> bool {
28        matches!(self, Self::Deserialize(_))
29    }
30}
31
32impl<D: fmt::Display> fmt::Display for StreamError<D> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Self::Database(e) => write!(f, "database error: {e}"),
36            Self::Deserialize(e) => write!(f, "deserialize error: {e}")
37        }
38    }
39}
40
41impl<D: std::error::Error + 'static> std::error::Error for StreamError<D> {
42    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
43        match self {
44            Self::Database(e) => Some(e),
45            Self::Deserialize(_) => None
46        }
47    }
48}
49
50#[cfg(test)]
51#[allow(clippy::uninlined_format_args)]
52mod tests {
53    use super::*;
54
55    #[derive(Debug)]
56    struct TestError(&'static str);
57
58    impl fmt::Display for TestError {
59        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60            write!(f, "{}", self.0)
61        }
62    }
63
64    impl std::error::Error for TestError {}
65
66    #[test]
67    fn stream_error_is_database() {
68        let err: StreamError<TestError> = StreamError::Database(TestError("db"));
69        assert!(err.is_database());
70        assert!(!err.is_deserialize());
71    }
72
73    #[test]
74    fn stream_error_is_deserialize() {
75        let err: StreamError<TestError> = StreamError::Deserialize("json".into());
76        assert!(err.is_deserialize());
77        assert!(!err.is_database());
78    }
79
80    #[test]
81    fn stream_error_display() {
82        let db: StreamError<TestError> = StreamError::Database(TestError("conn"));
83        assert_eq!(format!("{}", db), "database error: conn");
84
85        let de: StreamError<TestError> = StreamError::Deserialize("invalid".into());
86        assert_eq!(format!("{}", de), "deserialize error: invalid");
87    }
88
89    #[test]
90    fn stream_error_source() {
91        use std::error::Error;
92
93        let db: StreamError<TestError> = StreamError::Database(TestError("source"));
94        assert!(db.source().is_some());
95
96        let de: StreamError<TestError> = StreamError::Deserialize("no source".into());
97        assert!(de.source().is_none());
98    }
99}