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}