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)]
51mod tests {
52    use super::*;
53
54    #[derive(Debug)]
55    struct TestError(&'static str);
56
57    impl fmt::Display for TestError {
58        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59            write!(f, "{}", self.0)
60        }
61    }
62
63    impl std::error::Error for TestError {}
64
65    #[test]
66    fn stream_error_is_database() {
67        let err: StreamError<TestError> = StreamError::Database(TestError("db"));
68        assert!(err.is_database());
69        assert!(!err.is_deserialize());
70    }
71
72    #[test]
73    fn stream_error_is_deserialize() {
74        let err: StreamError<TestError> = StreamError::Deserialize("json".into());
75        assert!(err.is_deserialize());
76        assert!(!err.is_database());
77    }
78
79    #[test]
80    fn stream_error_display() {
81        let db: StreamError<TestError> = StreamError::Database(TestError("conn"));
82        assert_eq!(format!("{}", db), "database error: conn");
83
84        let de: StreamError<TestError> = StreamError::Deserialize("invalid".into());
85        assert_eq!(format!("{}", de), "deserialize error: invalid");
86    }
87
88    #[test]
89    fn stream_error_source() {
90        use std::error::Error;
91
92        let db: StreamError<TestError> = StreamError::Database(TestError("source"));
93        assert!(db.source().is_some());
94
95        let de: StreamError<TestError> = StreamError::Deserialize("no source".into());
96        assert!(de.source().is_none());
97    }
98}