alkblobs 0.2.0

Content-addressed blob storage: multi-hash put/get/verify store with pluggable backends, transport- and protocol-agnostic
Documentation
//! The read ops (store-api.md §Read path): `get`/`stat`/
//! `read_range`/`has`/`list` — every key-addressed op falling through
//! kv first, then large (the fall-through general rule), typed key
//! conversion at the facade boundary, verification-on-get on the kv
//! tier, and the `Vec<Key>` list shape.

use super::engines::AnyCursor;
use super::Store;
use super::{boundary_key, typed_keys};
use crate::backend::ReadCursor;
use crate::facade::{Blob, EntryMeta};
use crate::keys::Key;
use crate::Error;

/// The read path (store-api.md §Read path): `get`, `stat`,
/// `read_range`, `has`, `list` — every key-addressed op falling through
/// kv first, then large (ADR-012 §6.1's general rule), typed key
/// conversion at the facade boundary, and the module-doc design points
/// (the `Blob` stream shape, verification-on-get, the `Vec<Key>` list
/// shape) carried in their implementations.
impl Store {
    /// `get(key) → Option<(len, stream)>`. Falls through kv → large;
    /// a both-tier miss is `Ok(None)` — never `Error::Missing`
    /// (ADR-012 §6.5 ruling 5). On a kv hit the materialized read is
    /// whole-blob-verified against the key at get time (mismatch:
    /// `Err(Error::Verification)`, never a further fall-through) and
    /// the returned length is the verified materialized byte length;
    /// the large tier's hit opens streaming (metadata + handle, no
    /// canonical re-derivation — see the module doc), its length the
    /// cursor's metadata-only probe. Both hits return the same
    /// `(len, stream)` shape: the stream is [`Blob`] over the tier's
    /// cursor body.
    pub async fn get(&self, key: &[u8]) -> Result<Option<(u64, Blob)>, Error> {
        let key = boundary_key(key)?;
        let bytes = key.key_bytes();

        let cursor = if let Some(cursor) = self.kv().get(&bytes).await? {
            cursor
        } else if let Some(cursor) = self.large_get(&bytes).await? {
            cursor
        } else {
            return Ok(None);
        };

        match cursor {
            AnyCursor::Kv(kv) => {
                let value = kv.read_at(0, kv.len()).await?;
                let derived = key.algorithm().derive(&value, value.len() as u64);
                if &derived != key.digest() {
                    return Err(Error::Verification);
                }
                Ok(Some((value.len() as u64, Blob::new(AnyCursor::Kv(kv)))))
            }
            AnyCursor::Large(large) => Ok(Some((large.len(), Blob::new(AnyCursor::Large(large))))),
        }
    }

    /// The large tier's consult — `None` where the mode has no large
    /// tier (single-tier modes cannot hold large-tier content).
    async fn large_get(&self, bytes: &[u8]) -> Result<Option<AnyCursor>, Error> {
        match self.large() {
            Some(large) => large.get(bytes).await,
            None => Ok(None),
        }
    }

    /// `stat(key) → Option<EntryMeta>` — the cheap probe riding the
    /// trait's `size` method (ADR-008): length plus the key's hash
    /// algorithm, never a content load, falling through kv → large.
    /// A both-tier miss is `Ok(None)`.
    pub async fn stat(&self, key: &[u8]) -> Result<Option<EntryMeta>, Error> {
        let key = boundary_key(key)?;
        let bytes = key.key_bytes();

        let len = match self.kv().size(&bytes).await? {
            Some(len) => Some(len),
            None => match self.large() {
                Some(large) => large.size(&bytes).await?,
                None => None,
            },
        };
        Ok(len.map(|len| EntryMeta {
            len,
            algorithm: key.algorithm(),
        }))
    }

    /// `read_range(key, offset, len) → (slice, slice_digest)` — the
    /// slice plus the out-of-band SHA-256 slice digest (ADR-006, fixed
    /// by convention), composed through the cursor's `read_range` seam.
    /// Falls through kv → large exactly as [`Store::get`] does; no
    /// canonical verification rides a range (the slice digest is the
    /// whole answer, ADR-006 decision 2). A both-tier miss is
    /// `Err(Error::Missing)` — the pinned shape difference vs `get`'s
    /// `None` (ADR-012 §6.5 ruling 5).
    pub async fn read_range(
        &self,
        key: &[u8],
        offset: u64,
        len: u64,
    ) -> Result<(Vec<u8>, [u8; 32]), Error> {
        let key = boundary_key(key)?;
        let bytes = key.key_bytes();

        if let Some(cursor) = self.kv().get(&bytes).await? {
            return cursor.read_range(offset, len).await;
        }
        match self.large_get(&bytes).await? {
            Some(cursor) => cursor.read_range(offset, len).await,
            None => Err(Error::Missing),
        }
    }

    /// `has(key) → bool` — the pure existence probe (ADR-008's `has`
    /// discipline: an existence probe per tier, `local`'s body being
    /// the literal size-probe), falling through kv → large. Absent on
    /// both tiers: `false`. Virgin-store reads are no-ops.
    pub async fn has(&self, key: &[u8]) -> Result<bool, Error> {
        let key = boundary_key(key)?;
        let bytes = key.key_bytes();

        if self.kv().has(&bytes).await? {
            return Ok(true);
        }
        match self.large() {
            Some(large) => large.has(&bytes).await,
            None => Ok(false),
        }
    }

    /// `list() → Vec<Key>` — whole-pool enumeration of every tier,
    /// complete by contract (ADR-003), returned materialized and
    /// sorted in the keys' total order (the recorded Vec-vs-stream
    /// design point; see the module doc). Malformed pool bytes are
    /// fatal: `Err(Error::KeyInvalid)`, never a silent skip.
    ///
    /// Degenerate duplicates are not deduped on the public form: a
    /// key resident in both tiers (reachable only through the staged
    /// shapes that write a second home directly) lists once per home
    /// — the sweep's mark side dedups its candidate set, the public
    /// enumeration simply reflects each engine's contents.
    pub async fn list(&self) -> Result<Vec<Key>, Error> {
        let mut keys = typed_keys(self.kv().list().await?)?;
        if let Some(large) = self.large() {
            keys.extend(typed_keys(large.list().await?)?);
        }
        keys.sort();
        Ok(keys)
    }
}