Skip to main content

ObjectStore

Trait ObjectStore 

Source
pub trait ObjectStore: Send + Sync {
    // Required methods
    fn list<'life0, 'life1, 'async_trait>(
        &'life0 self,
        prefix: &'life1 str,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<ObjectMeta>, ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn get_to_file<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
        dest: &'life2 Path,
        opts: GetOpts,
    ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait;
    fn get_bytes<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
    ) -> Pin<Box<dyn Future<Output = Result<Bytes, ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn get_bytes_range<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
        range: Range<u64>,
    ) -> Pin<Box<dyn Future<Output = Result<Bytes, ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn put_bytes<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
        body: Bytes,
        opts: PutOpts,
    ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn put_if_absent<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
        body: Bytes,
    ) -> Pin<Box<dyn Future<Output = Result<bool, ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn head<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
    ) -> Pin<Box<dyn Future<Output = Result<ObjectMeta, ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn copy<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        src: &'life1 str,
        dst: &'life2 str,
    ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait;
    fn delete<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
    ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;

    // Provided methods
    fn put_path<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
        src: &'life2 Path,
        opts: PutOpts,
    ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait { ... }
    fn presigned_get_url<'life0, 'life1, 'async_trait>(
        &'life0 self,
        key: &'life1 str,
        ttl: Duration,
    ) -> Pin<Box<dyn Future<Output = Result<String, ObjectStoreError>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait { ... }
}
Expand description

Backend-neutral cloud object-store surface.

Method semantics — every implementation must satisfy these contracts so higher layers can target the trait without backend-specific branching.

  • list(prefix) — byte-prefix match (matches S3 Prefix= semantics; list("a") returns a, a/1, and aaa). Returns full keys; ordering is backend-defined.
  • get_to_file(key, dest, opts) — caller must ensure dest’s parent directory exists. opts.progress, if set, fires at chunk boundaries so callers (notably the LFS agent) can render a live progress bar.
  • put_bytes — overwrites if the key already exists.
  • put_path — streams a local file to the key, overwriting if present. Default reads the file into memory; backends should override for large-file streaming.
  • put_if_absent — returns Ok(true) on creation, Ok(false) if the key already existed. Backends collapse both 412 (PreconditionFailed) and 409 (Conflict) into Ok(false); transport-level failures still surface as Err.
  • copy(src, dst) — overwrites dst; returns Err(NotFound) when src is absent.
  • delete — returns Err(NotFound) on missing key. release_lock maps NotFound to Ok(()) and propagates other errors.
  • get_bytes_range — half-open [start, end) range. start == end returns Ok(Bytes::new()) with no network call. start > end and server-side 416 both surface as ObjectStoreError::RangeNotSatisfiable. When the object’s body ends inside the requested range (start < body.len() <= end) real S3/Azure backends return a silently truncated body (HTTP 206, fewer bytes than requested); this trait elevates that mismatch to ObjectStoreError::RangeNotSatisfiable so callers never see a short slice masquerading as the full requested range. The mock backend matches this contract.

Required Methods§

Source

fn list<'life0, 'life1, 'async_trait>( &'life0 self, prefix: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Vec<ObjectMeta>, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Enumerate every object whose key has prefix as a byte prefix.

Source

fn get_to_file<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, key: &'life1 str, dest: &'life2 Path, opts: GetOpts, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Stream the object body to dest. The destination’s parent directory must already exist. opts.progress, when set, fires at chunk boundaries with the count of bytes just received.

Source

fn get_bytes<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Bytes, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Read the entire object body into memory.

Source

fn get_bytes_range<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, range: Range<u64>, ) -> Pin<Box<dyn Future<Output = Result<Bytes, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Read a half-open byte range [start, end) of the object body.

start == end returns Ok(Bytes::new()) without issuing a network request. start > end, or a server-side 416, surfaces as ObjectStoreError::RangeNotSatisfiable.

Truncation contract: real S3 and Azure backends return a silently truncated body (HTTP 206 with fewer bytes than asked) when the requested range overruns the object’s end — start < body.len() <= end. Backends here elevate that mismatch to ObjectStoreError::RangeNotSatisfiable so a successful Ok(bytes) always carries exactly end - start bytes. The packchain reader (issue #52) relies on this to surface stale chain.json offsets and truncated pack files as data-integrity errors instead of pack-decode garbage.

Used by the packchain engine (issue #52) to read a single blob out of a larger pack file without downloading the whole pack.

Source

fn put_bytes<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, body: Bytes, opts: PutOpts, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Write body to key, overwriting any existing object.

Source

fn put_if_absent<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, body: Bytes, ) -> Pin<Box<dyn Future<Output = Result<bool, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Create key if and only if it does not exist. Returns Ok(true) when the object was created, Ok(false) when the key was already present.

Source

fn head<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<ObjectMeta, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Fetch metadata for an exact key.

Source

fn copy<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, src: &'life1 str, dst: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Copy src to dst. The body is preserved on every backend; user metadata is not guaranteed to survive — callers must not rely on metadata round-tripping through copy.

The trait’s only in-tree consumer is Doctor::evict_losing_bundle, which carries no user metadata on bundle objects.

Source

fn delete<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Delete key. Returns Err(ObjectStoreError::NotFound) if the key was not present.

Provided Methods§

Source

fn put_path<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, key: &'life1 str, src: &'life2 Path, opts: PutOpts, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Stream a local file to key, overwriting any existing object.

Backends should override this to stream from disk without buffering the entire file in process memory. The default implementation reads the file into memory and delegates to put_bytes; this is correct but defeats the streaming intent for large files.

Source

fn presigned_get_url<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ttl: Duration, ) -> Pin<Box<dyn Future<Output = Result<String, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Build a presigned (short-lived, signed) URL granting GET access to key for ttl. Used by the bundle-uri capability ([crate::protocol::bundle_uri]) to advertise time-limited download URLs against private buckets.

The default impl returns ObjectStoreError::Unsupported so backends that have no presigning model (e.g. MockStore in tests, or AzureStore configured with a TokenCredential rather than a shared account key) inherit a clean “not supported” error without needing a stub.

§Errors

Returns ObjectStoreError::Unsupported when the backend cannot produce signed URLs (default). Returns ObjectStoreError::Other when the backend supports presigning but the SDK rejects the TTL (e.g. AWS’s 7-day ceiling).

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl<T: ObjectStore + ?Sized> ObjectStore for Arc<T>

Blanket impl so &Arc<T> coerces to &dyn ObjectStore and so Arc<T> is itself usable as an ObjectStore value.

T: ObjectStore + ?Sized covers both concrete types (Arc<S3Store>, Arc<MockStore>) and erased trait objects (Arc<dyn ObjectStore>). Every method forwards to the inner T through the Deref impl.

Verified non-removable: dropping this impl breaks call sites that pass &Arc<dyn ObjectStore> to a function taking &dyn ObjectStore. Plain Arc::deref lets arc.list() work via Rust’s auto-deref on method calls, but the coercion &Arc<T>&dyn ObjectStore requires Arc<T> itself to implement the trait — and that is what this impl provides.

Source§

fn list<'life0, 'life1, 'async_trait>( &'life0 self, prefix: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Vec<ObjectMeta>, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn get_to_file<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, key: &'life1 str, dest: &'life2 Path, opts: GetOpts, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Source§

fn get_bytes<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Bytes, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn get_bytes_range<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, range: Range<u64>, ) -> Pin<Box<dyn Future<Output = Result<Bytes, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn put_bytes<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, body: Bytes, opts: PutOpts, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn put_path<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, key: &'life1 str, src: &'life2 Path, opts: PutOpts, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Source§

fn put_if_absent<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, body: Bytes, ) -> Pin<Box<dyn Future<Output = Result<bool, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn head<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<ObjectMeta, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn copy<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, src: &'life1 str, dst: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Source§

fn delete<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn presigned_get_url<'life0, 'life1, 'async_trait>( &'life0 self, key: &'life1 str, ttl: Duration, ) -> Pin<Box<dyn Future<Output = Result<String, ObjectStoreError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Implementors§