Skip to main content

auths_infra_git/
event_log.rs

1use auths_core::ports::storage::{EventLogReader, EventLogWriter, StorageError};
2use auths_verifier::keri::Prefix;
3
4use crate::error::map_git2_error;
5use crate::helpers;
6use crate::repo::GitRepo;
7
8const EVENT_FILE: &str = "event.json";
9
10/// Git-backed implementation of `EventLogReader` and `EventLogWriter`.
11///
12/// Events are stored as commits on `refs/keri/<prefix>/kel`. Each commit
13/// contains a single blob named `event.json`. The commit chain forms the
14/// ordered event log for that prefix.
15///
16/// Usage:
17/// ```ignore
18/// use auths_infra_git::{GitRepo, GitEventLog};
19/// use auths_core::ports::storage::EventLogReader;
20///
21/// let repo = GitRepo::open("/path/to/repo")?;
22/// let log = GitEventLog::new(&repo);
23/// let events = log.read_event_log("EAbcdef...")?;
24/// ```
25pub struct GitEventLog<'r> {
26    repo: &'r GitRepo,
27}
28
29impl<'r> GitEventLog<'r> {
30    pub fn new(repo: &'r GitRepo) -> Self {
31        Self { repo }
32    }
33
34    fn kel_ref(prefix: &str) -> String {
35        format!("refs/keri/{}/kel", prefix)
36    }
37}
38
39impl EventLogReader for GitEventLog<'_> {
40    fn read_event_log(&self, prefix: &Prefix) -> Result<Vec<u8>, StorageError> {
41        let refname = Self::kel_ref(prefix.as_str());
42        self.repo.with_repo(|repo| {
43            let events = walk_commits(repo, &refname)?;
44            let joined: Vec<u8> = events.into_iter().flatten().collect();
45            Ok(joined)
46        })
47    }
48
49    fn read_event_at(&self, prefix: &Prefix, seq: u64) -> Result<Vec<u8>, StorageError> {
50        let refname = Self::kel_ref(prefix.as_str());
51        self.repo.with_repo(|repo| {
52            let events = walk_commits(repo, &refname)?;
53            events
54                .into_iter()
55                .nth(seq as usize)
56                .ok_or_else(|| StorageError::not_found(format!("{}/seq/{}", prefix.as_str(), seq)))
57        })
58    }
59}
60
61impl EventLogWriter for GitEventLog<'_> {
62    fn append_event(&self, prefix: &Prefix, event: &[u8]) -> Result<(), StorageError> {
63        let refname = Self::kel_ref(prefix.as_str());
64        self.repo.with_repo(|repo| {
65            helpers::create_ref_commit(
66                repo,
67                &refname,
68                event,
69                EVENT_FILE,
70                &format!("append event to {}", prefix.as_str()),
71            )
72            .map_err(map_git2_error)?;
73            Ok(())
74        })
75    }
76}
77
78fn walk_commits(repo: &git2::Repository, refname: &str) -> Result<Vec<Vec<u8>>, StorageError> {
79    let reference = match repo.find_reference(refname) {
80        Ok(r) => r,
81        Err(e) if e.code() == git2::ErrorCode::NotFound => return Ok(Vec::new()),
82        Err(e) => return Err(map_git2_error(e)),
83    };
84
85    let mut commit = reference.peel_to_commit().map_err(map_git2_error)?;
86    let mut events = Vec::new();
87
88    loop {
89        let tree_oid = commit.tree_id();
90        match helpers::extract_blob_payload(repo, tree_oid, EVENT_FILE) {
91            Ok(data) => events.push(data),
92            Err(e) => {
93                log::warn!("skipping commit {}: {}", commit.id(), e);
94            }
95        }
96
97        if commit.parent_count() > 0 {
98            commit = commit.parent(0).map_err(map_git2_error)?;
99        } else {
100            break;
101        }
102    }
103
104    events.reverse();
105    Ok(events)
106}