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 S3Prefix=semantics;list("a")returnsa,a/1, andaaa). Returns full keys; ordering is backend-defined.get_to_file(key, dest, opts)— caller must ensuredest’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— returnsOk(true)on creation,Ok(false)if the key already existed. Backends collapse both 412 (PreconditionFailed) and 409 (Conflict) intoOk(false); transport-level failures still surface asErr.copy(src, dst)— overwritesdst; returnsErr(NotFound)whensrcis absent.delete— returnsErr(NotFound)on missing key.release_lockmapsNotFoundtoOk(())and propagates other errors.get_bytes_range— half-open[start, end)range.start == endreturnsOk(Bytes::new())with no network call.start > endand server-side 416 both surface asObjectStoreError::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 toObjectStoreError::RangeNotSatisfiableso callers never see a short slice masquerading as the full requested range. The mock backend matches this contract.
Required Methods§
Sourcefn 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 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.
Sourcefn 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_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.
Sourcefn 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<'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.
Sourcefn 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 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.
Sourcefn 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_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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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,
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§
Sourcefn 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 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.
Sourcefn 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,
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.
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.