Skip to main content

auths_infra_git/
blob_store.rs

1use auths_core::ports::storage::{BlobReader, BlobWriter, StorageError};
2
3use crate::error::map_git2_error;
4use crate::helpers;
5use crate::repo::GitRepo;
6
7const BLOB_FILE: &str = "data";
8
9/// Git-backed implementation of `BlobReader` and `BlobWriter`.
10///
11/// Stores blobs as single-file Git commits on refs derived from the
12/// logical path. For example, the path `"identities/abc123/metadata"`
13/// maps to the ref `refs/auths/blobs/identities/abc123/metadata`.
14///
15/// Usage:
16/// ```ignore
17/// use auths_infra_git::{GitRepo, GitBlobStore};
18/// use auths_core::ports::storage::BlobReader;
19///
20/// let repo = GitRepo::open("/path/to/repo")?;
21/// let store = GitBlobStore::new(&repo);
22/// let data = store.get_blob("identities/abc123/metadata")?;
23/// ```
24pub struct GitBlobStore<'r> {
25    repo: &'r GitRepo,
26}
27
28impl<'r> GitBlobStore<'r> {
29    pub fn new(repo: &'r GitRepo) -> Self {
30        Self { repo }
31    }
32
33    fn ref_for_path(path: &str) -> String {
34        format!("refs/auths/blobs/{}", path)
35    }
36}
37
38impl BlobReader for GitBlobStore<'_> {
39    fn get_blob(&self, path: &str) -> Result<Vec<u8>, StorageError> {
40        let refname = Self::ref_for_path(path);
41        self.repo.with_repo(|repo| {
42            let oid = helpers::resolve_git_ref(repo, &refname).map_err(map_git2_error)?;
43            let commit = repo.find_commit(oid).map_err(map_git2_error)?;
44            let tree_oid = commit.tree_id();
45            helpers::extract_blob_payload(repo, tree_oid, BLOB_FILE).map_err(map_git2_error)
46        })
47    }
48
49    fn list_blobs(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
50        let glob = format!("refs/auths/blobs/{}*", prefix);
51        self.repo.with_repo(|repo| {
52            let refs = helpers::list_refs_matching(repo, &glob).map_err(map_git2_error)?;
53            let strip_prefix = "refs/auths/blobs/";
54            Ok(refs
55                .into_iter()
56                .filter_map(|r| r.strip_prefix(strip_prefix).map(String::from))
57                .collect())
58        })
59    }
60
61    fn blob_exists(&self, path: &str) -> Result<bool, StorageError> {
62        let refname = Self::ref_for_path(path);
63        self.repo
64            .with_repo(|repo| match repo.find_reference(&refname) {
65                Ok(_) => Ok(true),
66                Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(false),
67                Err(e) => Err(map_git2_error(e)),
68            })
69    }
70}
71
72impl BlobWriter for GitBlobStore<'_> {
73    fn put_blob(&self, path: &str, data: &[u8]) -> Result<(), StorageError> {
74        let refname = Self::ref_for_path(path);
75        self.repo.with_repo(|repo| {
76            helpers::create_ref_commit(
77                repo,
78                &refname,
79                data,
80                BLOB_FILE,
81                &format!("put blob {}", path),
82            )
83            .map_err(map_git2_error)?;
84            Ok(())
85        })
86    }
87
88    fn delete_blob(&self, path: &str) -> Result<(), StorageError> {
89        let refname = Self::ref_for_path(path);
90        self.repo
91            .with_repo(|repo| match repo.find_reference(&refname) {
92                Ok(mut r) => {
93                    r.delete().map_err(map_git2_error)?;
94                    Ok(())
95                }
96                Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(()),
97                Err(e) => Err(map_git2_error(e)),
98            })
99    }
100}