use crate::{ChangeReport, Error, FieldCursor, Result};
use automerge::{ChangeHash, ObjId, Prop, ReadDoc, transaction::Transactable};
use uuid::Uuid;
use std::borrow::Cow;
#[derive(Debug, Clone, Default)]
pub struct Changes {
pub changed: bool,
}
impl ChangeReport for Changes {
fn any(&self) -> bool {
self.changed
}
fn paths(&self) -> Vec<Vec<Cow<'static, str>>> {
if self.changed { vec![vec![]] } else { vec![] }
}
fn leaf_paths(&self) -> Vec<Vec<Cow<'static, str>>> {
self.paths()
}
}
#[derive(Debug, Clone, Default)]
pub struct Cursor {
pub obj_id: Option<ObjId>,
}
impl FieldCursor for Cursor {
type Changes = Changes;
fn diff<D: ReadDoc>(&self, _doc: &D, obj: &ObjId) -> Result<Self::Changes> {
let changed = match &self.obj_id {
Some(cached) => cached != obj,
None => true,
};
Ok(Changes { changed })
}
fn refresh<D: ReadDoc>(&mut self, _doc: &D, obj: &ObjId) -> Result<()> {
self.obj_id = Some(obj.clone());
Ok(())
}
}
pub fn save<D: Transactable + ReadDoc>(
value: &Uuid,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let uuid_string = value.to_string();
crate::Automorph::save(&uuid_string, doc, obj, prop)
}
pub fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Uuid> {
let uuid_string: String = crate::Automorph::load(doc, obj, prop)?;
Uuid::parse_str(&uuid_string).map_err(|e| Error::invalid_value(format!("invalid UUID: {}", e)))
}
pub fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Uuid> {
let uuid_string: String = crate::Automorph::load_at(doc, obj, prop, heads)?;
Uuid::parse_str(&uuid_string).map_err(|e| Error::invalid_value(format!("invalid UUID: {}", e)))
}
pub fn diff<D: ReadDoc>(
value: &Uuid,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Changes> {
let stored: Result<Uuid> = load(doc, obj, prop);
match stored {
Ok(stored) => Ok(Changes { changed: *value != stored }),
Err(_) => Ok(Changes { changed: true }),
}
}
pub fn diff_at<D: ReadDoc>(
value: &Uuid,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Changes> {
let stored: Result<Uuid> = load_at(doc, obj, prop, heads);
match stored {
Ok(stored) => Ok(Changes { changed: *value != stored }),
Err(_) => Ok(Changes { changed: true }),
}
}
pub fn update<D: ReadDoc>(
value: &mut Uuid,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Changes> {
let loaded = load(doc, obj, prop)?;
let changed = *value != loaded;
*value = loaded;
Ok(Changes { changed })
}
pub fn update_at<D: ReadDoc>(
value: &mut Uuid,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Changes> {
let loaded = load_at(doc, obj, prop, heads)?;
let changed = *value != loaded;
*value = loaded;
Ok(Changes { changed })
}
#[cfg(test)]
mod tests {
use super::*;
use automerge::{AutoCommit, ROOT};
#[test]
fn test_uuid_roundtrip() {
let mut doc = AutoCommit::new();
let original = Uuid::new_v4();
save(&original, &mut doc, &ROOT, "id").unwrap();
let loaded = load(&doc, &ROOT, "id").unwrap();
assert_eq!(original, loaded);
}
#[test]
fn test_uuid_format() {
let mut doc = AutoCommit::new();
let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
save(&id, &mut doc, &ROOT, "id").unwrap();
let stored: String = crate::Automorph::load(&doc, &ROOT, "id").unwrap();
assert_eq!(stored, "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn test_uuid_nil() {
let mut doc = AutoCommit::new();
let nil = Uuid::nil();
save(&nil, &mut doc, &ROOT, "id").unwrap();
let loaded = load(&doc, &ROOT, "id").unwrap();
assert_eq!(nil, loaded);
assert!(loaded.is_nil());
}
#[test]
fn test_uuid_diff_no_change() {
let mut doc = AutoCommit::new();
let id = Uuid::new_v4();
save(&id, &mut doc, &ROOT, "id").unwrap();
let changes = diff(&id, &doc, &ROOT, "id").unwrap();
assert!(!changes.any());
}
#[test]
fn test_uuid_diff_with_change() {
let mut doc = AutoCommit::new();
let id1 = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let id2 = Uuid::parse_str("660e8400-e29b-41d4-a716-446655440001").unwrap();
save(&id1, &mut doc, &ROOT, "id").unwrap();
let changes = diff(&id2, &doc, &ROOT, "id").unwrap();
assert!(changes.any());
}
#[test]
fn test_uuid_update() {
let mut doc = AutoCommit::new();
let id1 = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let id2 = Uuid::parse_str("660e8400-e29b-41d4-a716-446655440001").unwrap();
save(&id1, &mut doc, &ROOT, "id").unwrap();
let mut current = id1;
let changes = update(&mut current, &doc, &ROOT, "id").unwrap();
assert!(!changes.any());
assert_eq!(current, id1);
save(&id2, &mut doc, &ROOT, "id").unwrap();
let changes = update(&mut current, &doc, &ROOT, "id").unwrap();
assert!(changes.any());
assert_eq!(current, id2);
}
#[test]
fn test_uuid_cursor() {
let mut doc = AutoCommit::new();
let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
save(&id, &mut doc, &ROOT, "id").unwrap();
let mut cursor = Cursor::default();
let (_, obj_id) = doc.get(&ROOT, "id").unwrap().unwrap();
cursor.refresh(&doc, &obj_id).unwrap();
let changes = cursor.diff(&doc, &obj_id).unwrap();
assert!(!changes.any());
let id2 = Uuid::parse_str("660e8400-e29b-41d4-a716-446655440001").unwrap();
save(&id2, &mut doc, &ROOT, "id").unwrap();
let (_, new_obj_id) = doc.get(&ROOT, "id").unwrap().unwrap();
let changes = cursor.diff(&doc, &new_obj_id).unwrap();
assert!(changes.any());
}
}