pub mod atomic_writer;
pub mod conflict;
pub mod json_file_store;
pub mod migration;
pub use conflict::FileMetadata;
pub use json_file_store::{JsonEnvelope, JsonFileStore};
use kanban_persistence::{PersistenceError, PersistenceStore, StoreFactory};
use std::sync::Arc;
pub struct JsonStoreFactory;
impl StoreFactory for JsonStoreFactory {
fn name(&self) -> &str {
"json"
}
fn matches_content(&self, header: &[u8]) -> bool {
let mut iter = header.iter();
if header.starts_with(&[0xEF, 0xBB, 0xBF]) {
iter = header[3..].iter();
}
let first = iter.find(|b| !b.is_ascii_whitespace());
matches!(first, Some(b'{') | Some(b'['))
}
fn create(
&self,
locator: &str,
) -> Result<Arc<dyn PersistenceStore + Send + Sync>, PersistenceError> {
Ok(Arc::new(JsonFileStore::new(locator)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matches_content_json_object() {
let factory = JsonStoreFactory;
assert!(factory.matches_content(b"{\"boards\": []}"));
}
#[test]
fn test_matches_content_json_array() {
let factory = JsonStoreFactory;
assert!(factory.matches_content(b"[1, 2, 3]"));
}
#[test]
fn test_matches_content_json_with_bom() {
let factory = JsonStoreFactory;
let mut data = vec![0xEF, 0xBB, 0xBF]; data.extend_from_slice(b" {\"key\": true}");
assert!(factory.matches_content(&data));
}
#[test]
fn test_matches_content_not_json() {
let factory = JsonStoreFactory;
assert!(!factory.matches_content(b"SQLite format 3\0"));
assert!(!factory.matches_content(b""));
assert!(!factory.matches_content(b"plain text"));
assert!(!factory.matches_content(b" \t\n "));
}
}