pub trait ArtifactStore: Send + Sync {
// Required methods
fn put(&self, payload: &[u8]) -> Result<ContentHash, ArtifactError>;
fn get(&self, hash: &ContentHash) -> Result<Vec<u8>, ArtifactError>;
fn list(&self) -> Result<Vec<ContentHash>, ArtifactError>;
fn contains(&self, hash: &ContentHash) -> Result<bool, ArtifactError>;
fn remove(&self, hash: &ContentHash) -> Result<bool, ArtifactError>;
// Provided method
fn get_to(
&self,
hash: &ContentHash,
out: &mut dyn Write,
) -> Result<u64, ArtifactError> { ... }
}Expand description
Artifact store trait. v0.3.7 ships an in-memory and a disk implementation.
Implementations MUST be Send + Sync so the store can be shared
across worker threads behind an Arc. They MUST also be safe under
concurrent put for the same payload — a put after a put for the
same content hash is idempotent.
Required Methods§
Sourcefn put(&self, payload: &[u8]) -> Result<ContentHash, ArtifactError>
fn put(&self, payload: &[u8]) -> Result<ContentHash, ArtifactError>
Insert payload into the store. The returned ContentHash is
blake3(payload); repeated calls with identical payloads return
identical hashes and overwrite the existing entry in place.
Sourcefn get(&self, hash: &ContentHash) -> Result<Vec<u8>, ArtifactError>
fn get(&self, hash: &ContentHash) -> Result<Vec<u8>, ArtifactError>
Fetch the payload previously inserted under hash. Returns
ArtifactError::NotFound on a genuine miss; integrity-failure
variants (ArtifactError::BadMagic, ArtifactError::BadVersion,
ArtifactError::BadHmac, ArtifactError::HashMismatch) are
returned when a record was present but the format checks rejected
it.
Sourcefn list(&self) -> Result<Vec<ContentHash>, ArtifactError>
fn list(&self) -> Result<Vec<ContentHash>, ArtifactError>
Enumerate the content hashes currently stored. Order is implementation-defined; callers that need a deterministic order must sort the result themselves.
Returns ArtifactError::Io on an underlying enumeration
failure (e.g. a read_dir permissions/I/O fault). This is
deliberately distinct from an empty result so GC/audit callers
can tell “store is empty” apart from “could not read the store”.
Sourcefn contains(&self, hash: &ContentHash) -> Result<bool, ArtifactError>
fn contains(&self, hash: &ContentHash) -> Result<bool, ArtifactError>
Cheap existence probe for hash: returns true if an entry is
present, false otherwise. This is deliberately a stat-only
check — it does NOT decode, decompress, or HMAC-verify the
record, so it is dramatically cheaper than Self::get and
suitable for the GC/audit roadmap’s “is this content already
resident?” question.
Because no integrity work happens, a true result means only
that a file (disk) or map entry (in-memory) exists under the
content-addressed key; a subsequent Self::get can still fail
with an integrity variant if that record was tampered with.
For DiskArtifactStore this probe is rotation-aware: it resolves
against every accepted read key (active or retired), so a blob
written under a now-retired key — and still visible to Self::get
— also reports true here, rather than appearing absent and leaking
past GC.
Returns ArtifactError::Io only on an underlying probe fault
that is neither “present” nor “absent” (e.g. a metadata call
failing for a reason other than not-found).
Sourcefn remove(&self, hash: &ContentHash) -> Result<bool, ArtifactError>
fn remove(&self, hash: &ContentHash) -> Result<bool, ArtifactError>
Remove the entry stored under hash. Returns true if a record
was removed, false if nothing was stored under hash (a
no-op delete is not an error — it mirrors HashMap::remove’s
“was it there?” boolean and the POSIX unlink-of-missing
convention GC callers expect).
For DiskArtifactStore this is rotation-aware: it resolves which
accepted key (active or retired) actually holds the blob — the same
resolution Self::get / Self::list use — and unlinks the
{hash}.{key_fp}.bin file (plus any sidecar) under THAT key, so a
blob written under a now-retired key can still be GC’d. For
InMemoryArtifactStore it drops the map entry. Returns
ArtifactError::Io on an underlying delete fault other than
not-found.
Provided Methods§
Sourcefn get_to(
&self,
hash: &ContentHash,
out: &mut dyn Write,
) -> Result<u64, ArtifactError>
fn get_to( &self, hash: &ContentHash, out: &mut dyn Write, ) -> Result<u64, ArtifactError>
Stream the verified-then-decoded body of hash into out,
returning the number of bytes written. This completes the
streaming story Self::put already has on the write side: where
Self::get returns an owned Vec<u8> (up to
MAX_DECOMPRESSED_LEN resident),
get_to lets a caller pipe a large artifact straight into a file,
socket, or hashing sink without first materialising the whole
decoded payload as a return value.
§Integrity ordering
The HMAC covers the entire compressed blob, so it cannot be
verified until the last body byte has been seen. To preserve the
crate-wide invariant — no unverified bytes are ever exposed to a
caller — implementations MUST authenticate the blob before
any decoded byte reaches out. DiskArtifactStore does this
with a two-pass scheme (pass 1: stream the compressed body through
the HMAC to verify; pass 2: re-open and stream-decode directly to
out), so peak heap stays bounded by the I/O buffers regardless
of payload size and out only ever sees authenticated bytes. The
error variants match Self::get.
The default implementation delegates to Self::get and copies
the resulting buffer into out; it is correct (the Vec get
returns is already verified) but not memory-bounded. Backends that
can stream override it.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".