aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/mod.rs
blob: 671bf5d1780cbd2bb75f2ffe9fd1843236c079d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//! The replicated document: the replica with its epoch, and the store semantics
//! (merged-view paths over JSON) the HTTP API and remakes work with.

/// The engine's types, under the names the rest of the crate uses: the document
/// (Automerge's `AutoCommit`), its changes and their hashes, and its writers (actors).
/// Only `document` and `remake` use Automerge itself (`architecture.md`, The document).
pub(crate) use automerge::{ActorId, AutoCommit as Document, Change, ChangeHash};

/// The whole document as bytes, as the engine saves it: a backup, the document
/// file's full save, the size a joining node downloads.
pub(crate) fn save(doc: &mut Document) -> Vec<u8> {
    doc.save()
}

/// The changes after `since` as bytes that, appended to a save, load with it (the
/// document file's appends).
pub(crate) fn save_since(doc: &mut Document, since: &[ChangeHash]) -> Vec<u8> {
    doc.save_after(since)
}

/// A saved document (`save`), loaded to write to under a writer ID of its own: one ID
/// must never write in two places, and a save may be loaded on several nodes, or again
/// after the node that saved it wrote on.
pub(crate) fn load(bytes: &[u8]) -> anyhow::Result<Document> {
    Ok(Document::load(bytes)?.with_actor(ActorId::random()))
}

/// The actor (writer ID) `doc` writes under.
pub(crate) fn actor(doc: &Document) -> ActorId {
    doc.get_actor().clone()
}

pub(crate) mod changes;
pub(crate) mod file;
pub(crate) mod lock;
pub(crate) mod meta;
pub(crate) mod replica;
pub(crate) mod store;
pub(crate) mod sync;

#[cfg(test)]
mod tests {
    use std::path::{Path, PathBuf};

    /// Every `.rs` file under `dir`.
    fn sources(dir: &Path, out: &mut Vec<PathBuf>) {
        for entry in std::fs::read_dir(dir).unwrap() {
            let path = entry.unwrap().path();
            if path.is_dir() {
                sources(&path, out);
            } else if path.extension().is_some_and(|extension| extension == "rs") {
                out.push(path);
            }
        }
    }

    /// Automerge is used only in `document` and `remake`; the rest of the node names
    /// its types as `document` re-exports them and works through `document`
    /// (`architecture.md`, The document).
    #[test]
    fn automerge_stays_behind_document() {
        let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
        let allowed = [src.join("document"), src.join("remake")];
        let mut files = Vec::new();
        sources(&src, &mut files);
        let mut found = Vec::new();
        for file in files
            .iter()
            .filter(|file| !allowed.iter().any(|allowed| file.starts_with(allowed)))
        {
            let text = std::fs::read_to_string(file).unwrap();
            for (number, line) in text.lines().enumerate() {
                if line.contains("automerge::") || line.trim_start().starts_with("use automerge") {
                    found.push(format!(
                        "{}:{}: {}",
                        file.display(),
                        number + 1,
                        line.trim()
                    ));
                }
            }
        }
        assert!(
            found.is_empty(),
            "Automerge used outside `document`:\n{}",
            found.join("\n")
        );
    }
}