Skip to main content

repolith_cache/
lib.rs

1//! Cache implementations for the repolith orchestration engine.
2//!
3//! The [`Cache`] trait and [`CacheError`] type are defined in
4//! [`repolith_core::cache`] and re-exported here for convenience. This crate
5//! hosts the concrete backends (`SQLite`, in-memory, …) that satisfy the trait.
6
7pub use repolith_core::cache::{Cache, CacheError, Result};
8
9/// SQLite-backed [`Cache`] implementation. See [`sqlite::SqliteCache`].
10pub mod sqlite;
11pub use sqlite::SqliteCache;
12
13/// Namespacing decorator — prefixes ids so multiple stacks can share one
14/// backend without collisions. See [`namespaced::NamespacedCache`].
15pub mod namespaced;
16pub use namespaced::NamespacedCache;
17
18/// Neo4j-backed [`Cache`] implementation (feature `neo4j`).
19/// See [`neo4j::Neo4jCache`].
20#[cfg(feature = "neo4j")]
21pub mod neo4j;
22#[cfg(feature = "neo4j")]
23pub use neo4j::{Neo4jCache, Neo4jConfig};
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28    use async_trait::async_trait;
29    use repolith_core::types::{ActionId, BuildEvent, Sha256};
30
31    struct MockCache {
32        events: std::collections::HashMap<ActionId, BuildEvent>,
33    }
34
35    impl MockCache {
36        fn new() -> Self {
37            Self {
38                events: std::collections::HashMap::new(),
39            }
40        }
41    }
42
43    #[async_trait]
44    impl Cache for MockCache {
45        async fn last_build(&self, id: &ActionId) -> Option<BuildEvent> {
46            self.events.get(id).cloned()
47        }
48
49        async fn record(&mut self, event: BuildEvent) -> Result<()> {
50            let id = match &event {
51                BuildEvent::Success { id, .. } | BuildEvent::Failed { id, .. } => id.clone(),
52            };
53            self.events.insert(id, event);
54            Ok(())
55        }
56    }
57
58    #[tokio::test]
59    async fn test_mock_cache() {
60        let mut cache = MockCache::new();
61        let id = ActionId("test-action".to_string());
62
63        // Initially empty
64        assert!(cache.last_build(&id).await.is_none());
65
66        // Record an event
67        let event = BuildEvent::Success {
68            id: id.clone(),
69            input: Sha256([0; 32]),
70            output: Sha256([1; 32]),
71            ms: 100,
72        };
73
74        cache.record(event.clone()).await.unwrap();
75
76        // Retrieve the event
77        let retrieved = cache.last_build(&id).await.unwrap();
78        match retrieved {
79            BuildEvent::Success { ms, .. } => assert_eq!(ms, 100),
80            BuildEvent::Failed { .. } => panic!("Expected Success event"),
81        }
82    }
83
84    #[test]
85    fn test_cache_error_display() {
86        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
87        let err1 = CacheError::Io(io_err);
88        assert_eq!(err1.to_string(), "io: file missing");
89
90        let err2 = CacheError::Backend("db locked".to_string());
91        assert_eq!(err2.to_string(), "backend: db locked");
92    }
93
94    #[test]
95    fn test_cache_is_object_safe() {
96        // This line will fail to compile if `Cache` is not object-safe.
97        let _cache: Box<dyn Cache> = Box::new(MockCache::new());
98    }
99}