kanban_persistence_json/
lib.rs1pub 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 if header.starts_with(&[0xEF, 0xBB, 0xBF]) {
23 iter = header[3..].iter();
24 }
25 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]; 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}