auths_infra_git/
event_log.rs1use auths_core::ports::storage::{EventLogReader, EventLogWriter, StorageError};
2use auths_keri::Prefix;
3use auths_keri::kel_io::KelStorageError;
4
5use crate::error::map_git2_error;
6use crate::helpers;
7use crate::repo::GitRepo;
8
9const EVENT_FILE: &str = "event.json";
10
11pub struct GitEventLog<'r> {
27 repo: &'r GitRepo,
28}
29
30impl<'r> GitEventLog<'r> {
31 pub fn new(repo: &'r GitRepo) -> Self {
32 Self { repo }
33 }
34
35 fn kel_ref(prefix: &str) -> String {
36 format!("refs/keri/{}/kel", prefix)
37 }
38}
39
40fn to_kel(e: StorageError) -> KelStorageError {
42 match e {
43 StorageError::NotFound { path } => KelStorageError::NotFound { path },
44 StorageError::AlreadyExists { path } => KelStorageError::AlreadyExists { path },
45 StorageError::CasConflict => KelStorageError::CasConflict,
46 StorageError::Io(s) => KelStorageError::Io(s),
47 StorageError::Internal(e) => KelStorageError::Internal(e),
48 other => KelStorageError::Internal(Box::new(other)),
50 }
51}
52
53impl EventLogReader for GitEventLog<'_> {
56 fn read_event_log(&self, prefix: &Prefix) -> Result<Vec<u8>, KelStorageError> {
57 let refname = Self::kel_ref(prefix.as_str());
58 self.repo
59 .with_repo(|repo| {
60 let events = walk_commits(repo, &refname)?;
61 let joined: Vec<u8> = events.into_iter().flatten().collect();
62 Ok(joined)
63 })
64 .map_err(to_kel)
65 }
66
67 fn read_event_at(&self, prefix: &Prefix, seq: u128) -> Result<Vec<u8>, KelStorageError> {
68 let refname = Self::kel_ref(prefix.as_str());
69 self.repo
70 .with_repo(|repo| {
71 let events = walk_commits(repo, &refname)?;
72 events.into_iter().nth(seq as usize).ok_or_else(|| {
73 StorageError::not_found(format!("{}/seq/{}", prefix.as_str(), seq))
74 })
75 })
76 .map_err(to_kel)
77 }
78}
79
80impl EventLogWriter for GitEventLog<'_> {
81 fn append_event(&self, prefix: &Prefix, event: &[u8]) -> Result<(), KelStorageError> {
82 let refname = Self::kel_ref(prefix.as_str());
83 self.repo
84 .with_repo(|repo| {
85 helpers::create_ref_commit(
86 repo,
87 &refname,
88 event,
89 EVENT_FILE,
90 &format!("append event to {}", prefix.as_str()),
91 )
92 .map_err(map_git2_error)?;
93 Ok(())
94 })
95 .map_err(to_kel)
96 }
97}
98
99fn walk_commits(repo: &git2::Repository, refname: &str) -> Result<Vec<Vec<u8>>, StorageError> {
100 let reference = match repo.find_reference(refname) {
101 Ok(r) => r,
102 Err(e) if e.code() == git2::ErrorCode::NotFound => return Ok(Vec::new()),
103 Err(e) => return Err(map_git2_error(e)),
104 };
105
106 let mut commit = reference.peel_to_commit().map_err(map_git2_error)?;
107 let mut events = Vec::new();
108
109 loop {
110 let tree_oid = commit.tree_id();
111 match helpers::extract_blob_payload(repo, tree_oid, EVENT_FILE) {
112 Ok(data) => events.push(data),
113 Err(e) => {
114 log::warn!("skipping commit {}: {}", commit.id(), e);
115 }
116 }
117
118 if commit.parent_count() > 0 {
119 commit = commit.parent(0).map_err(map_git2_error)?;
120 } else {
121 break;
122 }
123 }
124
125 events.reverse();
126 Ok(events)
127}