Skip to main content

kanban_persistence_json/
lib.rs

1pub mod atomic_writer;
2pub mod conflict;
3pub mod json_file_store;
4pub mod migration;
5
6pub use conflict::FileMetadata;
7pub use json_file_store::{JsonEnvelope, JsonFileStore};
8
9use kanban_persistence::{PersistenceError, PersistenceStore, StoreFactory};
10use std::sync::Arc;
11
12pub struct JsonStoreFactory;
13
14impl StoreFactory for JsonStoreFactory {
15    fn name(&self) -> &str {
16        "json"
17    }
18
19    fn matches_content(&self, header: &[u8]) -> bool {
20        let mut iter = header.iter();
21        // Skip UTF-8 BOM if present
22        if header.starts_with(&[0xEF, 0xBB, 0xBF]) {
23            iter = header[3..].iter();
24        }
25        // Skip whitespace, check for JSON start
26        let first = iter.find(|b| !b.is_ascii_whitespace());
27        matches!(first, Some(b'{') | Some(b'['))
28    }
29
30    fn create(
31        &self,
32        locator: &str,
33    ) -> Result<Arc<dyn PersistenceStore + Send + Sync>, PersistenceError> {
34        Ok(Arc::new(JsonFileStore::new(locator)))
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn test_matches_content_json_object() {
44        let factory = JsonStoreFactory;
45        assert!(factory.matches_content(b"{\"boards\": []}"));
46    }
47
48    #[test]
49    fn test_matches_content_json_array() {
50        let factory = JsonStoreFactory;
51        assert!(factory.matches_content(b"[1, 2, 3]"));
52    }
53
54    #[test]
55    fn test_matches_content_json_with_bom() {
56        let factory = JsonStoreFactory;
57        let mut data = vec![0xEF, 0xBB, 0xBF]; // UTF-8 BOM
58        data.extend_from_slice(b"  {\"key\": true}");
59        assert!(factory.matches_content(&data));
60    }
61
62    #[test]
63    fn test_matches_content_not_json() {
64        let factory = JsonStoreFactory;
65        assert!(!factory.matches_content(b"SQLite format 3\0"));
66        assert!(!factory.matches_content(b""));
67        assert!(!factory.matches_content(b"plain text"));
68        assert!(!factory.matches_content(b"   \t\n  "));
69    }
70}