Skip to main content

klieo_graph_projector/
error.rs

1//! Typed error returned by [`crate::EpisodeProjector`] operations.
2//!
3//! `#[non_exhaustive]` so additional failure modes (e.g. timeout,
4//! cancellation) can land in a 0.x minor without forcing match-arm
5//! updates on external callers. Each variant carries a boxed source
6//! so [`std::error::Error::source`] walks the chain.
7
8use klieo_core::error::MemoryError;
9use thiserror::Error;
10
11/// Error returned by [`crate::EpisodeProjector`] operations.
12///
13/// Variant docs name the call site that failed; access the wrapped
14/// error via [`std::error::Error::source`] (rather than the `source`
15/// field directly) so callers stay decoupled from the concrete inner
16/// type, which may evolve under the 0.x carve-out.
17#[derive(Debug, Error)]
18#[non_exhaustive]
19#[allow(missing_docs)] // variant `source` fields are reached via `Error::source`; no per-field contract beyond the variant doc above
20pub enum ProjectionError {
21    /// Entity extraction (the
22    /// [`klieo_memory_graph::EntityExtractor`] supplied to
23    /// [`crate::EpisodeProjector::new`]) returned an error.
24    #[error("entity extraction failed")]
25    Extraction {
26        #[source]
27        source: Box<dyn std::error::Error + Send + Sync + 'static>,
28    },
29    /// Indexing into the [`klieo_memory_graph::KnowledgeGraph`]
30    /// returned an error.
31    #[error("graph index failed")]
32    Graph {
33        #[source]
34        source: Box<dyn std::error::Error + Send + Sync + 'static>,
35    },
36    /// Replay against the supplied
37    /// [`klieo_core::memory::EpisodicMemory`] (only used by
38    /// [`crate::EpisodeProjector::project_run`]) returned an error.
39    #[error("episodic replay failed")]
40    Replay {
41        #[source]
42        source: Box<dyn std::error::Error + Send + Sync + 'static>,
43    },
44}
45
46impl ProjectionError {
47    pub(crate) fn extraction(err: MemoryError) -> Self {
48        Self::Extraction {
49            source: Box::new(err),
50        }
51    }
52    pub(crate) fn graph(err: MemoryError) -> Self {
53        Self::Graph {
54            source: Box::new(err),
55        }
56    }
57    pub(crate) fn replay(err: MemoryError) -> Self {
58        Self::Replay {
59            source: Box::new(err),
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::error::Error as _;
68
69    fn sample_memory_error(label: &str) -> MemoryError {
70        MemoryError::Store(label.to_string())
71    }
72
73    #[test]
74    fn extraction_variant_chains_source_to_memory_error() {
75        let err = ProjectionError::extraction(sample_memory_error("extractor exploded"));
76        let source = err.source().expect("Extraction must expose a source");
77        let inner = source
78            .downcast_ref::<MemoryError>()
79            .expect("source must downcast to MemoryError");
80        assert!(matches!(inner, MemoryError::Store(s) if s.contains("extractor exploded")));
81        assert_eq!(format!("{err}"), "entity extraction failed");
82    }
83
84    #[test]
85    fn graph_variant_chains_source_to_memory_error() {
86        let err = ProjectionError::graph(sample_memory_error("graph backend exploded"));
87        let source = err.source().expect("Graph must expose a source");
88        let inner = source
89            .downcast_ref::<MemoryError>()
90            .expect("source must downcast to MemoryError");
91        assert!(matches!(inner, MemoryError::Store(s) if s.contains("graph backend exploded")));
92        assert_eq!(format!("{err}"), "graph index failed");
93    }
94
95    #[test]
96    fn replay_variant_chains_source_to_memory_error() {
97        let err = ProjectionError::replay(sample_memory_error("episodic exploded"));
98        let source = err.source().expect("Replay must expose a source");
99        let inner = source
100            .downcast_ref::<MemoryError>()
101            .expect("source must downcast to MemoryError");
102        assert!(matches!(inner, MemoryError::Store(s) if s.contains("episodic exploded")));
103        assert_eq!(format!("{err}"), "episodic replay failed");
104    }
105}