Skip to main content

loonfs_objectstore/
object_store.rs

1//! The [`ObjectStore`] contract every provider implements, plus its
2//! shared value and error types.
3
4use async_trait::async_trait;
5use bytes::Bytes;
6use futures::stream::{BoxStream, TryStreamExt};
7use loonfs_api::StorageChecksum;
8use serde::{Deserialize, Serialize};
9use std::fmt::Debug;
10use std::sync::Arc;
11use thiserror::Error;
12
13/// Shares one provider client across handles without changing its storage semantics.
14pub type SharedObjectStore = Arc<dyn ObjectStore>;
15
16/// A payload delivered in pieces, for writes that must not hold it whole.
17///
18/// Chunk boundaries carry no meaning: an implementation regroups them into
19/// whatever units the provider wants. A chunk error ends the write, and the
20/// implementation cleans up whatever it had started.
21pub type ByteStream = BoxStream<'static, Result<Bytes>>;
22
23/// Metadata returned by a successful `head`, full-object `get`, or `put` call.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct ObjectMetadata {
26    /// Opaque compare token for one object version.
27    ///
28    /// This is suitable for immediate compare-and-swap on the same object key. It is not
29    /// canonical content identity and callers must not derive provider-specific meaning from it.
30    pub etag: Option<String>,
31    /// Provider version identifier when available.
32    pub version: Option<String>,
33    /// Complete object length in bytes at the observed version.
34    pub size_bytes: u64,
35    /// Provider last-modified time in unix milliseconds, when available.
36    ///
37    /// Advisory: garbage collection uses it for grace/reap age checks and
38    /// treats an absent value as "young" (retain). Never a validity input.
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub last_modified_ms: Option<u64>,
41}
42
43/// Size and the stored full-object checksum for one object, read from a
44/// single provider metadata request.
45///
46/// This is the evidence a completion check needs to decide whether the
47/// object at a key is the object that was promised, without downloading it.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct StoredObjectChecksum {
50    /// Complete object length the provider reports.
51    pub size_bytes: u64,
52    /// Full-object checksum the provider stored with the object.
53    pub storage_checksum: StorageChecksum,
54}
55
56/// One part of a client-driven multipart upload, as the client observed the
57/// provider accept it.
58///
59/// LoonFS keeps no durable record of any part. Parts are the uploader's
60/// bookkeeping, exactly as they are in the provider's own multipart API, and
61/// this is the shape they come back in at completion.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct MultipartPart {
64    /// One-based part number.
65    pub part_number: u32,
66    /// Entity tag the provider returned for the accepted part.
67    pub etag: String,
68    /// Checksum the part was signed and accepted with.
69    pub checksum: StorageChecksum,
70}
71
72/// What a provider said about an attempt to assemble a multipart upload.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74pub enum MultipartCompletion {
75    /// The provider accepted the assembly on this call.
76    Assembled,
77    /// The provider has no such upload. It was already consumed — by an
78    /// earlier completion whose response was lost, or by an abort — so the
79    /// object at the key, if any, is the only remaining evidence of what
80    /// happened. Providers disagree about this case (AWS S3 replays a
81    /// success carrying no checksum, Cloudflare R2 answers `NoSuchUpload`),
82    /// which is exactly why the caller resolves it from the object instead.
83    UnknownUpload,
84}
85
86/// Full object bytes returned with metadata from the same read operation.
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct ObjectBody {
89    /// Identity, size, and modification metadata observed with these exact bytes.
90    pub metadata: ObjectMetadata,
91    /// Complete object payload from the same read as `metadata`.
92    pub bytes: Vec<u8>,
93}
94
95/// Controls the write semantics of a `put` call.
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97pub enum PutMode {
98    /// Unconditionally overwrite any existing object.
99    Overwrite,
100    /// Write only if the key does not already exist. Returns `PreconditionFailed` if it does.
101    CreateIfAbsent,
102    /// Write only if the current etag matches. Returns `PreconditionFailed` on mismatch.
103    CompareAndSwap {
104        /// Opaque token returned by a prior observation of this same key.
105        expected_etag: String,
106    },
107}
108
109/// A byte range for partial object reads.
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111pub struct ByteRange {
112    /// First byte to read (inclusive, zero-based).
113    pub start_inclusive: u64,
114    /// First byte to exclude (exclusive, zero-based).
115    pub end_exclusive: u64,
116}
117
118/// Failure of one object-store operation.
119///
120/// Every object-scoped variant names the object it is about via `object_key`
121/// (for list operations, the listed prefix). The exceptions are deliberate:
122/// `InvalidContentRef` fails before a key exists, `Unsupported` is about a
123/// store capability, and `Configuration` is about store construction.
124#[derive(Debug, Error)]
125#[non_exhaustive]
126pub enum ObjectStoreError {
127    /// Reports a required object that was absent, distinct from optional-read `None`.
128    #[error("object not found `{object_key}`")]
129    NotFound {
130        /// Logical object key that was required.
131        object_key: String,
132    },
133    /// Reports a logical key that is empty, escaping, malformed, or otherwise outside its scope.
134    #[error("invalid object key `{object_key}`: {message}")]
135    InvalidKey {
136        /// Caller-supplied key rejected before provider IO.
137        object_key: String,
138        /// Specific key-validation failure.
139        message: String,
140    },
141    /// Not object-scoped: the content ref never resolved to an object key.
142    #[error("invalid content ref: {0}")]
143    InvalidContentRef(String),
144    /// Reports a byte range whose bounds cannot select a valid position in the object.
145    #[error("invalid byte range for `{object_key}`")]
146    InvalidRange {
147        /// Object for which the caller supplied invalid range bounds.
148        object_key: String,
149    },
150    /// Reports a create-if-absent or compare-and-swap condition that did not hold.
151    #[error("precondition failed for `{object_key}`")]
152    PreconditionFailed {
153        /// Object whose current state disagreed with the requested write mode.
154        object_key: String,
155    },
156    /// The provider rejected the caller's identity or authorization —
157    /// wrong, expired, or insufficient credentials. Configuration-shaped
158    /// and never transient: retrying cannot help, an operator can.
159    #[error("permission denied for `{object_key}`: {message}")]
160    PermissionDenied {
161        /// Object or listing prefix the provider refused to authorize.
162        object_key: String,
163        /// Sanitized provider explanation with credential material removed.
164        message: String,
165    },
166    /// Not object-scoped: the store lacks a required capability.
167    #[error("unsupported capability: {0}")]
168    Unsupported(&'static str),
169    /// Not object-scoped: store construction or configuration failed before
170    /// any object was addressed.
171    #[error("invalid object store configuration: {0}")]
172    Configuration(String),
173    /// Reports an IO, timeout, protocol, or provider failure with ambiguous completion.
174    #[error("transport error for `{object_key}`: {message}")]
175    Transport {
176        /// Object or listing prefix whose operation did not complete observably.
177        object_key: String,
178        /// Sanitized provider or local-IO diagnostic.
179        message: String,
180    },
181}
182
183impl ObjectStoreError {
184    /// Builds a [`ObjectStoreError::Transport`] for an operation on `object_key`.
185    pub fn transport(object_key: impl Into<String>, message: impl Into<String>) -> Self {
186        Self::Transport {
187            object_key: object_key.into(),
188            message: message.into(),
189        }
190    }
191
192    /// Key of the object (or listed prefix) the failing operation targeted,
193    /// when the failure is object-scoped.
194    pub fn object_key(&self) -> Option<&str> {
195        match self {
196            Self::NotFound { object_key }
197            | Self::InvalidKey { object_key, .. }
198            | Self::InvalidRange { object_key }
199            | Self::PreconditionFailed { object_key }
200            | Self::PermissionDenied { object_key, .. }
201            | Self::Transport { object_key, .. } => Some(object_key),
202            Self::InvalidContentRef(_) | Self::Unsupported(_) | Self::Configuration(_) => None,
203        }
204    }
205
206    /// Failure text without the object key, for wrappers that record the key
207    /// as their own structured field.
208    pub fn message(&self) -> String {
209        match self {
210            Self::NotFound { .. } => "object not found".to_owned(),
211            Self::InvalidKey { message, .. } => format!("invalid object key: {message}"),
212            Self::InvalidContentRef(message) => format!("invalid content ref: {message}"),
213            Self::InvalidRange { .. } => "invalid byte range".to_owned(),
214            Self::PreconditionFailed { .. } => "precondition failed".to_owned(),
215            Self::PermissionDenied { message, .. } => {
216                format!("permission denied: {message}")
217            }
218            Self::Unsupported(capability) => format!("unsupported capability: {capability}"),
219            Self::Configuration(message) => {
220                format!("invalid object store configuration: {message}")
221            }
222            Self::Transport { message, .. } => message.clone(),
223        }
224    }
225}
226
227/// Facade alias: signatures inside this crate use `Result<T>`.
228pub type Result<T> = std::result::Result<T, ObjectStoreError>;
229
230/// Drains a byte stream into one buffer, for implementations that cannot
231/// write incrementally.
232pub(crate) async fn collect_stream(mut body: ByteStream) -> Result<Bytes> {
233    use futures::StreamExt as _;
234
235    let mut buffered = bytes::BytesMut::new();
236    while let Some(chunk) = body.next().await {
237        buffered.extend_from_slice(&chunk?);
238    }
239    Ok(buffered.freeze())
240}
241
242/// Defines the provider-independent durability and consistency boundary LoonFS relies on.
243///
244/// Implementations must satisfy the
245/// [required guarantees](../../../docs/specs/format.md#11-required-guarantees).
246#[async_trait]
247pub trait ObjectStore: Send + Sync + Debug {
248    /// Reads metadata for one key, returning `None` when the object is absent.
249    ///
250    /// The returned compare token belongs to this exact observation. Invalid
251    /// keys and provider failures are returned as [`ObjectStoreError`].
252    async fn head(&self, key: &str) -> Result<Option<ObjectMetadata>>;
253
254    /// Reads size and the stored full-object checksum for one key, returning
255    /// `None` when the object is absent.
256    ///
257    /// This is exactly one metadata request. S3-family stores issue
258    /// `HeadObject` with checksum mode enabled; `GetObjectAttributes` is
259    /// never used anywhere, because Cloudflare R2 answers it with 501 and
260    /// code that reaches for it passes its tests against S3 and fails in
261    /// production.
262    ///
263    /// Stores that cannot report a stored checksum return
264    /// [`ObjectStoreError::Unsupported`]. That is the same capability line
265    /// as presigned direct uploads: a deployment whose provider cannot show
266    /// the checksum back also cannot offer `direct_put`, because completion
267    /// would have nothing to verify against.
268    async fn head_stored_checksum(&self, key: &str) -> Result<Option<StoredObjectChecksum>> {
269        let _ = key;
270        Err(ObjectStoreError::Unsupported(
271            "stored full-object checksum readback",
272        ))
273    }
274
275    /// Opens a provider multipart upload targeting `key`, whose eventual
276    /// checksum covers the whole assembled object.
277    ///
278    /// This is the control half of a client-driven multipart upload: the
279    /// bytes travel from the client straight to the provider under signed
280    /// per-part capabilities, and the store only opens, closes, and abandons
281    /// the upload. Stores that cannot express it return
282    /// [`ObjectStoreError::Unsupported`].
283    async fn create_multipart_upload(&self, key: &str) -> Result<String> {
284        let _ = key;
285        Err(ObjectStoreError::Unsupported(
286            "client-driven multipart upload",
287        ))
288    }
289
290    /// Asks the provider to assemble `parts` into the object at `key`.
291    ///
292    /// `full_object_checksum` is supplied as a precondition where the
293    /// provider honours one. It is not sufficient evidence on its own:
294    /// Cloudflare R2 accepts a wrong claim, assembles the object, and
295    /// reports the true checksum, so a caller must read the object's stored
296    /// checksum back before believing anything about its bytes.
297    async fn complete_multipart_upload(
298        &self,
299        key: &str,
300        provider_upload_id: &str,
301        parts: &[MultipartPart],
302        full_object_checksum: &StorageChecksum,
303    ) -> Result<MultipartCompletion> {
304        let (_, _, _, _) = (key, provider_upload_id, parts, full_object_checksum);
305        Err(ObjectStoreError::Unsupported(
306            "client-driven multipart upload",
307        ))
308    }
309
310    /// Abandons a provider multipart upload and the parts it accumulated.
311    ///
312    /// Aborting an upload that already completed is safe on every provider
313    /// LoonFS supports: it succeeds and leaves the assembled object alone.
314    /// An upload the provider has never heard of also succeeds, so cleanup
315    /// can run without first proving what state it is cleaning up.
316    async fn abort_multipart_upload(&self, key: &str, provider_upload_id: &str) -> Result<()> {
317        let _ = (key, provider_upload_id);
318        Err(ObjectStoreError::Unsupported(
319            "client-driven multipart upload",
320        ))
321    }
322
323    /// Reads complete bytes and identity metadata from one self-consistent observation.
324    ///
325    /// Returns `None` when the object is absent; invalid keys and provider
326    /// failures are returned as [`ObjectStoreError`].
327    async fn get_with_metadata(&self, key: &str) -> Result<Option<ObjectBody>>;
328
329    /// Reads a full object or one half-open byte range, returning `None` when absent.
330    ///
331    /// A range ending beyond the object is truncated; a descending range or
332    /// start beyond the object returns [`ObjectStoreError::InvalidRange`].
333    async fn get(&self, key: &str, range: Option<ByteRange>) -> Result<Option<Bytes>>;
334
335    /// Writes bytes under the requested overwrite or provider-enforced precondition.
336    ///
337    /// Successful completion is immediately authoritative. Invalid keys,
338    /// failed conditions, permission failures, and ambiguous transport failures are returned.
339    async fn put(&self, key: &str, bytes: Bytes, mode: PutMode) -> Result<ObjectMetadata>;
340
341    /// Writes a payload of unknown length without holding it whole, and
342    /// reports how many bytes were stored.
343    ///
344    /// This is for immutable objects at uniquely-named keys — content
345    /// blobs. Two properties define it:
346    ///
347    /// - **Bounded memory.** A store that can write incrementally holds at
348    ///   most one internal buffer at a time, whatever the payload's length.
349    ///   The default implementation cannot, and says so below.
350    /// - **The body is consumed before any precondition is evaluated.** A
351    ///   caller folding a digest over the stream as it forwards it therefore
352    ///   always ends up with a digest over the complete payload, even when
353    ///   the write is refused — which is what lets it tell "these are the
354    ///   same bytes again" from "these are different bytes".
355    ///
356    /// `mode` is honoured exactly while the payload fits inside one
357    /// internal part. Beyond that, the write goes through the provider's
358    /// multipart upload, whose completion is an unconditional overwrite, so
359    /// a create-only or compare-and-swap request degrades to one there.
360    /// That is the same trade [`Self::put_immutable_verified`] already makes
361    /// at its multipart threshold, and the reason both are for immutable
362    /// keys only: on a key whose bytes are fixed by its name, the condition
363    /// is a corruption tripwire rather than a concurrency control.
364    ///
365    /// A failed or abandoned write leaves no provider state behind: an
366    /// implementation that opened a multipart upload aborts it.
367    ///
368    /// The default implementation **buffers the whole stream** and delegates
369    /// to [`Self::put`]. It exists so a provider without an incremental
370    /// write is honest rather than absent: its memory cost is exactly what
371    /// buffering the payload and calling `put` costs today, and it is
372    /// bounded only by whatever bounds the caller puts on the stream.
373    async fn put_streamed(&self, key: &str, body: ByteStream, mode: PutMode) -> Result<u64> {
374        let bytes = collect_stream(body).await?;
375        let size_bytes = bytes.len() as u64;
376        self.put(key, bytes, mode).await?;
377        Ok(size_bytes)
378    }
379
380    /// Deletes a key idempotently and makes its absence immediately authoritative.
381    ///
382    /// Missing objects succeed; invalid keys, permission failures, and
383    /// ambiguous transport failures are returned.
384    async fn delete(&self, key: &str) -> Result<()>;
385
386    /// Streams keys under `prefix` in ascending lexicographic order.
387    ///
388    /// Invalid prefixes and listing failures arrive as stream items.
389    fn list_prefix_stream(&self, prefix: &str) -> BoxStream<'static, Result<String>>;
390
391    /// Collects and sorts every key under `prefix`.
392    ///
393    /// The operation fails if prefix validation or any streamed provider page fails.
394    async fn list_prefix(&self, prefix: &str) -> Result<Vec<String>> {
395        let mut keys: Vec<String> = self.list_prefix_stream(prefix).try_collect().await?;
396        keys.sort();
397        Ok(keys)
398    }
399
400    /// Writes bytes unconditionally, replacing any existing object at `key`.
401    ///
402    /// Invalid keys, permission failures, and ambiguous transport failures are returned.
403    async fn put_overwrite(&self, key: &str, bytes: Bytes) -> Result<ObjectMetadata> {
404        self.put(key, bytes, PutMode::Overwrite).await
405    }
406
407    /// Creates `key` only when no object is present.
408    ///
409    /// Existing objects return [`ObjectStoreError::PreconditionFailed`];
410    /// invalid keys, permission failures, and transport failures are also returned.
411    async fn put_if_absent(&self, key: &str, bytes: Bytes) -> Result<ObjectMetadata> {
412        self.put(key, bytes, PutMode::CreateIfAbsent).await
413    }
414
415    /// Writes `bytes` under an immutable `key` and accepts success only when
416    /// the key contains exactly those bytes.
417    ///
418    /// Payloads below [`crate::PROVIDER_MULTIPART_THRESHOLD_BYTES`] use
419    /// create-if-absent; payloads at or above that threshold use the store's
420    /// multipart-capable overwrite path. Transport retries are safe only
421    /// because every writer allowed to name this immutable key must supply
422    /// identical bytes. Mutable keys must use [`Self::put`] and own their
423    /// protocol-specific ambiguity resolution.
424    async fn put_immutable_verified(
425        &self,
426        key: &str,
427        bytes: Bytes,
428    ) -> std::result::Result<(), crate::ImmutableWriteError> {
429        crate::immutable_write::put(self, key, bytes).await
430    }
431
432    /// Replaces `key` only while its current opaque token equals `expected_etag`.
433    ///
434    /// A missing object or stale token returns
435    /// [`ObjectStoreError::PreconditionFailed`]; invalid keys, permission
436    /// failures, and transport failures are also returned.
437    async fn compare_and_swap(
438        &self,
439        key: &str,
440        expected_etag: &str,
441        bytes: Bytes,
442    ) -> Result<ObjectMetadata> {
443        self.put(
444            key,
445            bytes,
446            PutMode::CompareAndSwap {
447                expected_etag: expected_etag.to_owned(),
448            },
449        )
450        .await
451    }
452}
453
454#[async_trait]
455impl<T: ObjectStore + ?Sized> ObjectStore for Arc<T> {
456    async fn head(&self, key: &str) -> Result<Option<ObjectMetadata>> {
457        self.as_ref().head(key).await
458    }
459
460    async fn head_stored_checksum(&self, key: &str) -> Result<Option<StoredObjectChecksum>> {
461        self.as_ref().head_stored_checksum(key).await
462    }
463
464    async fn create_multipart_upload(&self, key: &str) -> Result<String> {
465        self.as_ref().create_multipart_upload(key).await
466    }
467
468    async fn complete_multipart_upload(
469        &self,
470        key: &str,
471        provider_upload_id: &str,
472        parts: &[MultipartPart],
473        full_object_checksum: &StorageChecksum,
474    ) -> Result<MultipartCompletion> {
475        self.as_ref()
476            .complete_multipart_upload(key, provider_upload_id, parts, full_object_checksum)
477            .await
478    }
479
480    async fn abort_multipart_upload(&self, key: &str, provider_upload_id: &str) -> Result<()> {
481        self.as_ref()
482            .abort_multipart_upload(key, provider_upload_id)
483            .await
484    }
485
486    async fn get_with_metadata(&self, key: &str) -> Result<Option<ObjectBody>> {
487        self.as_ref().get_with_metadata(key).await
488    }
489
490    async fn get(&self, key: &str, range: Option<ByteRange>) -> Result<Option<Bytes>> {
491        self.as_ref().get(key, range).await
492    }
493
494    async fn put(&self, key: &str, bytes: Bytes, mode: PutMode) -> Result<ObjectMetadata> {
495        self.as_ref().put(key, bytes, mode).await
496    }
497
498    async fn put_streamed(&self, key: &str, body: ByteStream, mode: PutMode) -> Result<u64> {
499        self.as_ref().put_streamed(key, body, mode).await
500    }
501
502    async fn delete(&self, key: &str) -> Result<()> {
503        self.as_ref().delete(key).await
504    }
505
506    fn list_prefix_stream(&self, prefix: &str) -> BoxStream<'static, Result<String>> {
507        self.as_ref().list_prefix_stream(prefix)
508    }
509
510    async fn list_prefix(&self, prefix: &str) -> Result<Vec<String>> {
511        self.as_ref().list_prefix(prefix).await
512    }
513
514    async fn put_overwrite(&self, key: &str, bytes: Bytes) -> Result<ObjectMetadata> {
515        self.as_ref().put_overwrite(key, bytes).await
516    }
517
518    async fn put_if_absent(&self, key: &str, bytes: Bytes) -> Result<ObjectMetadata> {
519        self.as_ref().put_if_absent(key, bytes).await
520    }
521
522    async fn put_immutable_verified(
523        &self,
524        key: &str,
525        bytes: Bytes,
526    ) -> std::result::Result<(), crate::ImmutableWriteError> {
527        self.as_ref().put_immutable_verified(key, bytes).await
528    }
529
530    async fn compare_and_swap(
531        &self,
532        key: &str,
533        expected_etag: &str,
534        bytes: Bytes,
535    ) -> Result<ObjectMetadata> {
536        self.as_ref()
537            .compare_and_swap(key, expected_etag, bytes)
538            .await
539    }
540}
541
542#[async_trait]
543impl<T: ObjectStore + ?Sized> ObjectStore for &T {
544    async fn head(&self, key: &str) -> Result<Option<ObjectMetadata>> {
545        (*self).head(key).await
546    }
547
548    async fn head_stored_checksum(&self, key: &str) -> Result<Option<StoredObjectChecksum>> {
549        (*self).head_stored_checksum(key).await
550    }
551
552    async fn create_multipart_upload(&self, key: &str) -> Result<String> {
553        (*self).create_multipart_upload(key).await
554    }
555
556    async fn complete_multipart_upload(
557        &self,
558        key: &str,
559        provider_upload_id: &str,
560        parts: &[MultipartPart],
561        full_object_checksum: &StorageChecksum,
562    ) -> Result<MultipartCompletion> {
563        (*self)
564            .complete_multipart_upload(key, provider_upload_id, parts, full_object_checksum)
565            .await
566    }
567
568    async fn abort_multipart_upload(&self, key: &str, provider_upload_id: &str) -> Result<()> {
569        (*self)
570            .abort_multipart_upload(key, provider_upload_id)
571            .await
572    }
573
574    async fn get_with_metadata(&self, key: &str) -> Result<Option<ObjectBody>> {
575        (*self).get_with_metadata(key).await
576    }
577
578    async fn get(&self, key: &str, range: Option<ByteRange>) -> Result<Option<Bytes>> {
579        (*self).get(key, range).await
580    }
581
582    async fn put(&self, key: &str, bytes: Bytes, mode: PutMode) -> Result<ObjectMetadata> {
583        (*self).put(key, bytes, mode).await
584    }
585
586    async fn put_streamed(&self, key: &str, body: ByteStream, mode: PutMode) -> Result<u64> {
587        (*self).put_streamed(key, body, mode).await
588    }
589
590    async fn delete(&self, key: &str) -> Result<()> {
591        (*self).delete(key).await
592    }
593
594    fn list_prefix_stream(&self, prefix: &str) -> BoxStream<'static, Result<String>> {
595        (*self).list_prefix_stream(prefix)
596    }
597
598    async fn list_prefix(&self, prefix: &str) -> Result<Vec<String>> {
599        (*self).list_prefix(prefix).await
600    }
601
602    async fn put_overwrite(&self, key: &str, bytes: Bytes) -> Result<ObjectMetadata> {
603        (*self).put_overwrite(key, bytes).await
604    }
605
606    async fn put_if_absent(&self, key: &str, bytes: Bytes) -> Result<ObjectMetadata> {
607        (*self).put_if_absent(key, bytes).await
608    }
609
610    async fn put_immutable_verified(
611        &self,
612        key: &str,
613        bytes: Bytes,
614    ) -> std::result::Result<(), crate::ImmutableWriteError> {
615        (*self).put_immutable_verified(key, bytes).await
616    }
617
618    async fn compare_and_swap(
619        &self,
620        key: &str,
621        expected_etag: &str,
622        bytes: Bytes,
623    ) -> Result<ObjectMetadata> {
624        (*self).compare_and_swap(key, expected_etag, bytes).await
625    }
626}
627
628#[cfg(test)]
629mod tests {
630    use super::*;
631    use futures::stream;
632    use std::sync::atomic::{AtomicBool, Ordering};
633
634    #[derive(Debug)]
635    struct ListOverrideStore {
636        override_reached: Arc<AtomicBool>,
637    }
638
639    #[async_trait]
640    impl ObjectStore for ListOverrideStore {
641        async fn head(&self, _key: &str) -> Result<Option<ObjectMetadata>> {
642            Ok(None)
643        }
644
645        async fn get_with_metadata(&self, _key: &str) -> Result<Option<ObjectBody>> {
646            Ok(None)
647        }
648
649        async fn get(&self, _key: &str, _range: Option<ByteRange>) -> Result<Option<Bytes>> {
650            Ok(None)
651        }
652
653        async fn put(&self, key: &str, _bytes: Bytes, _mode: PutMode) -> Result<ObjectMetadata> {
654            Err(ObjectStoreError::PreconditionFailed {
655                object_key: key.to_owned(),
656            })
657        }
658
659        async fn delete(&self, _key: &str) -> Result<()> {
660            Ok(())
661        }
662
663        fn list_prefix_stream(&self, _prefix: &str) -> BoxStream<'static, Result<String>> {
664            Box::pin(stream::empty())
665        }
666
667        async fn list_prefix(&self, _prefix: &str) -> Result<Vec<String>> {
668            self.override_reached.store(true, Ordering::SeqCst);
669            Ok(vec!["overridden".to_owned()])
670        }
671    }
672
673    #[tokio::test]
674    async fn arc_dyn_store_forwards_overridden_list_prefix() {
675        let override_reached = Arc::new(AtomicBool::new(false));
676        let store: Arc<dyn ObjectStore> = Arc::new(ListOverrideStore {
677            override_reached: Arc::clone(&override_reached),
678        });
679
680        let keys = <Arc<dyn ObjectStore> as ObjectStore>::list_prefix(&store, "prefix/")
681            .await
682            .expect("overridden list should succeed");
683
684        assert_eq!(keys, vec!["overridden"]);
685        assert!(override_reached.load(Ordering::SeqCst));
686    }
687}