Skip to main content

noxu_persist/
error.rs

1//! Error types for the persistence layer.
2//!
3//! # v1.5.1 cleanup (Wave 1C, persist-xa Low audit)
4//!
5//! Four `PersistError` variants used to live here that no production
6//! call site ever returned: `EntityNotFound`, `DuplicateKey`,
7//! `InvalidEntity`, `StoreAlreadyOpen`.  They were removed because
8//! the public surface advertises an error variant the engine cannot
9//! actually emit.  Migration:
10//!
11//! * `EntityNotFound` — `PrimaryIndex::get` returns `Result<Option<E>>`
12//!   already; absence is `Ok(None)`, never an error.
13//! * `DuplicateKey` — `PrimaryIndex::put` is overwrite-by-default; if
14//!   the user wants "insert or fail", they should use the
15//!   `Database::put_no_overwrite` shape and check the
16//!   `OperationStatus::KeyExist` status bit instead.
17//! * `InvalidEntity` — entity validation is the application's
18//!   responsibility; raise an application-level error type or
19//!   `PersistError::SerializationError` instead.
20//! * `StoreAlreadyOpen` — `EntityStore::open` cannot fail with this:
21//!   each call constructs a fresh handle.
22
23use thiserror::Error;
24
25/// Errors that can occur in the persistence layer.
26///
27#[derive(Debug, Error)]
28pub enum PersistError {
29    /// An error from the underlying database layer.
30    #[error("database error: {0}")]
31    DatabaseError(#[from] noxu_db::NoxuError),
32
33    /// An error occurred during serialization or deserialization.
34    #[error("serialization error: {0}")]
35    SerializationError(String),
36
37    /// The entity store is not open.
38    #[error("store not open")]
39    StoreNotOpen,
40
41    /// The requested index is not available.
42    #[error("index not available: {0}")]
43    IndexNotAvailable(String),
44
45    /// A primary write was performed inside a user transaction while the
46    /// `PrimaryIndex` had registered (in-memory) secondary indexes.
47    ///
48    /// In v1.5 DPL secondary indexes are in-memory only and their updates
49    /// are NOT atomic with the surrounding transaction: secondary mutations
50    /// are applied immediately on the primary `put` / `delete_with_entity`
51    /// call regardless of whether the user later commits or aborts the
52    /// transaction.  This variant is **not** returned from those methods —
53    /// they continue to succeed — but it is constructed and emitted as a
54    /// `log::warn!` (one-shot per `PrimaryIndex`) so operators have a
55    /// machine-greppable signal that the limitation applies to them.
56    ///
57    /// Tracking issue: persistent secondaries are scoped for v1.6.
58    #[error(
59        "DPL secondary indexes are in-memory only in v1.5; secondary \
60         updates are not atomic with the user transaction (see \
61         docs/src/collections/entity-persistence.md, v1.5 limitations)"
62    )]
63    SecondariesNotTransactional,
64}
65
66/// Result type for persistence layer operations.
67pub type Result<T> = std::result::Result<T, PersistError>;
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_serialization_error_display() {
75        let err = PersistError::SerializationError("bad format".to_string());
76        assert_eq!(err.to_string(), "serialization error: bad format");
77    }
78
79    #[test]
80    fn test_store_not_open_display() {
81        let err = PersistError::StoreNotOpen;
82        assert_eq!(err.to_string(), "store not open");
83    }
84
85    #[test]
86    fn test_index_not_available_display() {
87        let err = PersistError::IndexNotAvailable("email_idx".to_string());
88        assert_eq!(err.to_string(), "index not available: email_idx");
89    }
90
91    #[test]
92    fn test_secondaries_not_transactional_display() {
93        let err = PersistError::SecondariesNotTransactional;
94        let msg = err.to_string();
95        assert!(msg.contains("secondary indexes"));
96        assert!(msg.contains("in-memory"));
97    }
98
99    #[test]
100    fn test_database_error_from() {
101        let db_err = noxu_db::NoxuError::DatabaseClosed;
102        let err: PersistError = db_err.into();
103        assert!(matches!(err, PersistError::DatabaseError(_)));
104        assert!(err.to_string().contains("database error"));
105    }
106}