Skip to main content

somatize_store/
store.rs

1//! Who keeps what is worth keeping. The hole.
2
3use crate::Digest;
4use std::fmt;
5
6/// What you want to remember about something you stored, in the order you say
7/// it. Text and not a closed type because the vocabulary is the caller's; what
8/// this crate does with it is write it down and hand it back.
9pub type Meta = Vec<(String, String)>;
10
11/// A name, what it points at, and what was said about it.
12#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
13pub struct Bound {
14    /// What it is called: a cache key, an artifact's id.
15    pub name: String,
16    /// The bytes it points at.
17    pub digest: Digest,
18    /// What the caller wanted to remember.
19    pub meta: Meta,
20    /// When it was bound, in seconds since the epoch. Stamped here because a
21    /// store you cannot sort by time is one you cannot explore.
22    pub when: u64,
23}
24
25/// Keeps bytes by their content, and names that point at them.
26pub trait Store: Send + Sync {
27    /// Saves these bytes. Saving the same ones twice is the same as saving them
28    /// once — that is what content addressing is for.
29    fn put(&self, bytes: &[u8]) -> Result<Digest, StoreError>;
30
31    /// The bytes, if they are here.
32    fn get(&self, digest: &Digest) -> Result<Option<Vec<u8>>, StoreError>;
33
34    /// Points a name at some bytes, with what you want to remember about it.
35    ///
36    /// Binding the same name again replaces it: a name is the question, and the
37    /// answer can be refreshed — which is what `.overwrite()` will do.
38    fn bind(&self, name: &str, digest: &Digest, meta: Meta) -> Result<(), StoreError>;
39
40    /// Points a name at some bytes **only if nobody has**, and says whether it
41    /// did. This is how work gets handed out.
42    ///
43    /// Not `resolve` then `bind`: between the two somebody else does the same,
44    /// and two machines train the same round while nobody trains the next. Hence
45    /// on the trait with no default — one written out of the other two would be
46    /// a race with a doc comment on it.
47    fn claim(&self, name: &str, digest: &Digest, meta: Meta) -> Result<bool, StoreError>;
48
49    /// What that name points at, if anything.
50    fn resolve(&self, name: &str) -> Result<Option<Bound>, StoreError>;
51
52    /// The same for many at once, in the order they were asked. In the trait
53    /// from the first day: a cache that works item by item asks thousands at a
54    /// time, which against a remote store is thousands of round trips unless it
55    /// is one call. The default is the loop.
56    fn resolve_many(&self, names: &[&str]) -> Result<Vec<Option<Bound>>, StoreError> {
57        names.iter().map(|name| self.resolve(name)).collect()
58    }
59
60    /// The same for the bytes.
61    fn get_many(&self, digests: &[&Digest]) -> Result<Vec<Option<Vec<u8>>>, StoreError> {
62        digests.iter().map(|digest| self.get(digest)).collect()
63    }
64
65    /// Everything bound here. A scan, and that is the point: the records are the
66    /// truth, and an index that answers faster is built from them and thrown
67    /// away.
68    fn bound(&self) -> Result<Vec<Bound>, StoreError>;
69}
70
71/// One record, in the JSON it is kept as — readable with `cat`, which was a
72/// requirement before it was a format. Here and not in an implementor, because
73/// it is what makes a directory and a bucket the same store.
74pub(crate) fn record(name: &str, digest: &Digest, meta: Meta) -> Result<Vec<u8>, StoreError> {
75    let bound = Bound {
76        name: name.to_string(),
77        digest: digest.clone(),
78        meta,
79        when: std::time::SystemTime::now()
80            .duration_since(std::time::UNIX_EPOCH)
81            .map(|since| since.as_secs())
82            .unwrap_or(0),
83    };
84    serde_json::to_vec_pretty(&bound)
85        .map_err(|e| StoreError::Corrupt(format!("that record cannot be written: {e}")))
86}
87
88/// The other direction.
89pub(crate) fn read_record(bytes: &[u8]) -> Result<Bound, StoreError> {
90    serde_json::from_slice(bytes)
91        .map_err(|e| StoreError::Corrupt(format!("that record cannot be read: {e}")))
92}
93
94/// Why something could not be kept, or found.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum StoreError {
97    /// The system said no: no permission, no space, no such directory.
98    Io(String),
99    /// Something is there and is not what it should be.
100    Corrupt(String),
101}
102
103impl fmt::Display for StoreError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Self::Io(why) => write!(f, "the store could not be reached: {why}"),
107            Self::Corrupt(why) => write!(f, "what the store has is not what it should be: {why}"),
108        }
109    }
110}
111
112impl std::error::Error for StoreError {}