1#[derive(Debug, thiserror::Error)]
19#[non_exhaustive]
20pub enum MemoryError {
21 #[error("sqlx error: {0}")]
22 Sqlx(#[from] zeph_db::SqlxError),
23
24 #[error("db error: {0}")]
25 Db(#[from] zeph_db::DbError),
26
27 #[error("Qdrant error: {0}")]
28 Qdrant(#[from] Box<qdrant_client::QdrantError>),
29
30 #[error("vector store error: {0}")]
31 VectorStore(#[from] crate::vector_store::VectorStoreError),
32
33 #[error("LLM error: {0}")]
34 Llm(#[from] zeph_llm::LlmError),
35
36 #[error("JSON error: {0}")]
37 Json(#[from] serde_json::Error),
38
39 #[error("integer conversion: {0}")]
40 IntConversion(#[from] std::num::TryFromIntError),
41
42 #[error("snapshot error: {0}")]
43 Snapshot(String),
44
45 #[error("I/O error: {0}")]
46 Io(#[from] std::io::Error),
47
48 #[error("graph store error: {0}")]
49 GraphStore(String),
50
51 #[error("invalid input: {0}")]
52 InvalidInput(String),
53
54 #[error("lock poisoned: {0}")]
59 LockPoisoned(String),
60
61 #[error("{0}")]
68 Other(String),
69
70 #[error("operation timed out: {0}")]
71 Timeout(String),
72
73 #[error("supersede cycle detected at edge id={0}")]
75 SupersedeCycle(i64),
76
77 #[error("supersede chain depth cap exceeded at edge id={0}")]
79 SupersedeDepthExceeded(i64),
80
81 #[error("promotion scan failed: {0}")]
85 Promotion(String),
86
87 #[error("ingest error: {0}")]
92 Ingest(String),
93
94 #[error("validation rejected: {0}")]
100 ValidationRejected(String),
101}
102
103impl MemoryError {
104 #[must_use]
110 pub fn is_foreign_key_violation(&self) -> bool {
111 use sqlx::error::DatabaseError;
112
113 matches!(self, Self::Sqlx(err) if err
114 .as_database_error()
115 .is_some_and(DatabaseError::is_foreign_key_violation))
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::MemoryError;
122
123 #[test]
124 fn sqlx_variant_display() {
125 let inner = zeph_db::SqlxError::RowNotFound;
126 let err = MemoryError::Sqlx(inner);
127 assert!(
128 err.to_string().starts_with("sqlx error:"),
129 "unexpected display: {err}"
130 );
131 }
132
133 #[test]
134 fn db_variant_display() {
135 let inner = zeph_db::DbError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
136 let err = MemoryError::Db(inner);
137 assert!(
138 err.to_string().starts_with("db error:"),
139 "unexpected display: {err}"
140 );
141 }
142
143 #[test]
144 fn is_foreign_key_violation_false_for_non_constraint_error() {
145 let err = MemoryError::Sqlx(zeph_db::SqlxError::RowNotFound);
146 assert!(!err.is_foreign_key_violation());
147 }
148
149 #[test]
150 fn is_foreign_key_violation_false_for_non_sqlx_variant() {
151 let err = MemoryError::GraphStore("unrelated failure".into());
152 assert!(!err.is_foreign_key_violation());
153 }
154
155 #[tokio::test]
159 async fn is_foreign_key_violation_true_for_real_fk_violation() {
160 use crate::graph::GraphStore;
161 use crate::store::SqliteStore;
162
163 let sqlite = SqliteStore::new(":memory:").await.unwrap();
164 let store = GraphStore::new(sqlite.pool().clone());
165
166 let err = store
168 .insert_edge(999_999, 888_888, "related_to", "fact", 0.9, None, None)
169 .await
170 .expect_err("inserting an edge between nonexistent entities must fail");
171
172 assert!(
173 err.is_foreign_key_violation(),
174 "expected a foreign-key violation, got: {err:#}"
175 );
176 }
177}