Skip to main content

acyclic_objects/
provider.rs

1//! Transport-neutral provider contract and deterministic process-local reference implementation.
2
3use std::{
4    collections::{BTreeMap, BTreeSet},
5    sync::Arc,
6};
7
8#[cfg(feature = "local")]
9use std::path::PathBuf;
10
11use async_trait::async_trait;
12use tokio::sync::Mutex;
13
14use crate::{limits, wire};
15
16const MEMORY_BYTES: usize = 64 * 1_024 * 1_024;
17const SYSTEM_METADATA_BYTES: usize = 2 * 1_024;
18
19/// Exactly one current-version mutation condition shared by every provider transport.
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub enum Condition {
22    /// Require no live current version.
23    IfAbsent,
24    /// Require the current opaque `ETag`.
25    IfMatch(String),
26    /// Require the current opaque version identity.
27    IfVersion(String),
28}
29
30#[cfg(any(feature = "grpc", feature = "local"))]
31impl Condition {
32    pub(crate) fn wire(self) -> wire::Preconditions {
33        let condition = match self {
34            Self::IfAbsent => wire::preconditions::Condition::IfAbsent(true),
35            Self::IfMatch(etag) => wire::preconditions::Condition::IfMatch(etag),
36            Self::IfVersion(version_id) => wire::preconditions::Condition::IfVersion(version_id),
37        };
38        wire::Preconditions {
39            condition: Some(condition),
40        }
41    }
42}
43
44/// A bucket or immutable snapshot selected for a read.
45#[derive(Clone, Debug, PartialEq)]
46pub enum ReadTarget {
47    /// Read the current state of this bucket identity.
48    Bucket(wire::BucketRef),
49    /// Read the captured state of this snapshot identity.
50    Snapshot(wire::SnapshotRef),
51}
52
53/// One transport-independent put request.
54#[derive(Clone, Debug)]
55pub struct PutRequest {
56    /// Exact bucket identity and display name.
57    pub bucket: wire::BucketRef,
58    /// UTF-8 object key.
59    pub object_key: String,
60    /// Complete body bytes.
61    pub body: bytes::Bytes,
62    /// Immutable metadata for the new version.
63    pub metadata: wire::ObjectMetadata,
64    /// Optional current-version precondition.
65    pub condition: Option<Condition>,
66    /// Optional original-request idempotency key.
67    pub idempotency_key: Option<String>,
68}
69
70/// One transport-independent object read.
71#[derive(Clone, Debug)]
72pub struct GetRequest {
73    /// Current bucket or immutable snapshot.
74    pub target: ReadTarget,
75    /// UTF-8 object key.
76    pub object_key: String,
77    /// Exact retained version, or the visible current version when absent.
78    pub version_id: Option<String>,
79    /// One inclusive byte range, or the complete body when absent.
80    pub range: Option<(u64, Option<u64>)>,
81    /// Optional representation validator that must match.
82    pub if_match: Option<String>,
83    /// Optional representation validator that must not match.
84    pub if_none_match: Option<String>,
85    /// Maximum selected body bytes the provider may allocate or return.
86    pub maximum_bytes: u64,
87}
88
89/// One immutable descriptor and its selected body bytes.
90#[derive(Clone, Debug, PartialEq)]
91pub struct BufferedObject {
92    /// Immutable version descriptor.
93    pub version: wire::ObjectVersion,
94    /// Complete body or requested range.
95    pub body: bytes::Bytes,
96}
97
98/// Result of an unqualified marker creation or exact-version deletion.
99#[derive(Clone, Debug, PartialEq)]
100pub struct DeleteResult {
101    /// Whether an exact addressed version existed. Marker creation is always `true`.
102    pub existed: bool,
103    /// Newly created marker for an unqualified delete.
104    pub marker: Option<wire::ObjectVersion>,
105}
106
107/// One stable page over a captured listing view.
108#[derive(Clone, Debug, PartialEq)]
109pub struct ProviderListPage {
110    /// Object/version entries in contract order.
111    pub entries: Vec<wire::ListEntry>,
112    /// Delimiter-grouped prefixes in lexical order.
113    pub common_prefixes: Vec<String>,
114    /// Opaque continuation for the same immutable view.
115    pub continuation: Option<String>,
116}
117
118/// Provider-visible semantic failure. Transport errors remain adapter-specific.
119#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
120pub enum ObjectsError {
121    /// Input violates the public contract.
122    #[error("invalid objects request: {0}")]
123    Invalid(&'static str),
124    /// The addressed bucket, key, version, or snapshot does not exist.
125    #[error("objects resource not found")]
126    NotFound,
127    /// A bucket name or fork destination already exists.
128    #[error("objects resource already exists")]
129    AlreadyExists,
130    /// A current-version condition did not hold and no mutation occurred.
131    #[error("objects precondition failed")]
132    PreconditionFailed,
133    /// An idempotency key was reused with different arguments.
134    #[error("objects idempotency mismatch")]
135    IdempotencyMismatch,
136    /// A fixed or provider-specific bound was exhausted.
137    #[error("objects capacity exhausted")]
138    Capacity,
139    /// The provider does not implement the requested capability.
140    #[error("objects capability is unsupported")]
141    Unsupported,
142    /// Authentication or authorization was rejected.
143    #[error("objects provider rejected authorization")]
144    Unauthorized,
145    /// The provider could not be reached or returned an invalid response.
146    #[error("objects provider is unavailable")]
147    Unavailable,
148}
149
150/// Public semantic provider implemented by memory, customer-hosted, and Acyclic adapters.
151#[async_trait]
152pub trait ObjectsProvider: Send + Sync {
153    /// Create one permanently versioned bucket.
154    async fn create_bucket(
155        &self,
156        name: String,
157        idempotency_key: Option<String>,
158    ) -> Result<wire::Bucket, ObjectsError>;
159    /// Resolve one exact bucket identity.
160    async fn head_bucket(&self, bucket: &wire::BucketRef) -> Result<wire::Bucket, ObjectsError>;
161    /// Delete one empty exact bucket identity.
162    async fn delete_bucket(
163        &self,
164        bucket: &wire::BucketRef,
165        idempotency_key: Option<String>,
166    ) -> Result<bool, ObjectsError>;
167    /// Create one immutable version.
168    async fn put(&self, request: PutRequest) -> Result<wire::ObjectVersion, ObjectsError>;
169    /// Read one immutable version or its visible current selection.
170    async fn get(&self, request: GetRequest) -> Result<BufferedObject, ObjectsError>;
171    /// Create a delete marker or delete one exact version.
172    async fn delete(
173        &self,
174        bucket: wire::BucketRef,
175        object_key: String,
176        version_id: Option<String>,
177        condition: Option<Condition>,
178        idempotency_key: Option<String>,
179    ) -> Result<DeleteResult, ObjectsError>;
180    /// Capture or continue one stable listing.
181    async fn list(
182        &self,
183        target: ReadTarget,
184        prefix: String,
185        delimiter: Option<String>,
186        versions: bool,
187        page_size: u32,
188        continuation: Option<String>,
189    ) -> Result<ProviderListPage, ObjectsError>;
190    /// Atomically capture an immutable whole-bucket snapshot.
191    async fn snapshot(
192        &self,
193        bucket: wire::BucketRef,
194        idempotency_key: Option<String>,
195    ) -> Result<wire::Snapshot, ObjectsError>;
196    /// Destroy one exact snapshot identity.
197    async fn destroy_snapshot(
198        &self,
199        snapshot: wire::SnapshotRef,
200        idempotency_key: Option<String>,
201    ) -> Result<bool, ObjectsError>;
202    /// Atomically fork a whole bucket or snapshot into an independent bucket.
203    async fn fork(
204        &self,
205        source: ReadTarget,
206        destination_name: String,
207        idempotency_key: Option<String>,
208    ) -> Result<wire::Bucket, ObjectsError>;
209    /// Create one multipart upload without publishing a version.
210    async fn create_multipart(
211        &self,
212        bucket: wire::BucketRef,
213        object_key: String,
214        metadata: wire::ObjectMetadata,
215        condition: Option<Condition>,
216        idempotency_key: Option<String>,
217    ) -> Result<wire::MultipartUpload, ObjectsError>;
218    /// Upload or replace one exact staged part.
219    async fn upload_part(
220        &self,
221        bucket: wire::BucketRef,
222        object_key: String,
223        upload_id: String,
224        part_number: u32,
225        body: bytes::Bytes,
226        idempotency_key: Option<String>,
227    ) -> Result<wire::UploadedPart, ObjectsError>;
228    /// List staged parts in ascending part-number order.
229    async fn list_parts(
230        &self,
231        bucket: wire::BucketRef,
232        object_key: String,
233        upload_id: String,
234    ) -> Result<Vec<wire::UploadedPart>, ObjectsError>;
235    /// Atomically publish a version from exact staged part receipts.
236    async fn complete_multipart(
237        &self,
238        bucket: wire::BucketRef,
239        object_key: String,
240        upload_id: String,
241        parts: Vec<wire::UploadedPart>,
242        idempotency_key: Option<String>,
243    ) -> Result<wire::ObjectVersion, ObjectsError>;
244    /// Abort one exact staged upload.
245    async fn abort_multipart(
246        &self,
247        bucket: wire::BucketRef,
248        object_key: String,
249        upload_id: String,
250        idempotency_key: Option<String>,
251    ) -> Result<bool, ObjectsError>;
252}
253
254#[derive(Clone)]
255enum StoredBody {
256    Memory(bytes::Bytes),
257    Composite {
258        parts: Arc<[StoredBody]>,
259        length: usize,
260    },
261    #[cfg(feature = "local")]
262    Local {
263        root: Arc<PathBuf>,
264        digest: [u8; 32],
265        length: usize,
266    },
267}
268
269#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
270enum BodyIdentity {
271    Memory {
272        address: usize,
273        length: usize,
274    },
275    #[cfg(feature = "local")]
276    Local([u8; 32]),
277}
278
279impl StoredBody {
280    fn memory(body: bytes::Bytes) -> Self {
281        Self::Memory(body)
282    }
283
284    fn len(&self) -> usize {
285        match self {
286            Self::Memory(body) => body.len(),
287            Self::Composite { length, .. } => *length,
288            #[cfg(feature = "local")]
289            Self::Local { length, .. } => *length,
290        }
291    }
292
293    fn read(&self, start: usize, end: usize) -> Result<bytes::Bytes, ObjectsError> {
294        match self {
295            Self::Memory(body) => Ok(body.slice(start..end)),
296            Self::Composite { parts, .. } => {
297                let mut output = Vec::with_capacity(end.saturating_sub(start));
298                let mut offset = 0usize;
299                for part in parts.iter() {
300                    let part_end = offset
301                        .checked_add(part.len())
302                        .ok_or(ObjectsError::Unavailable)?;
303                    if part_end > start && offset < end {
304                        let selected_start = start.saturating_sub(offset).min(part.len());
305                        let selected_end = end.saturating_sub(offset).min(part.len());
306                        output.extend_from_slice(&part.read(selected_start, selected_end)?);
307                    }
308                    offset = part_end;
309                }
310                if output.len() != end.saturating_sub(start) {
311                    return Err(ObjectsError::Unavailable);
312                }
313                Ok(output.into())
314            }
315            #[cfg(feature = "local")]
316            Self::Local {
317                root,
318                digest,
319                length,
320            } => {
321                if end > *length || start > end {
322                    return Err(ObjectsError::Invalid("invalid range"));
323                }
324                crate::local::read_body(root, digest, *length, start, end)
325            }
326        }
327    }
328
329    fn update_hash(&self, hasher: &mut blake3::Hasher) -> Result<(), ObjectsError> {
330        match self {
331            Self::Memory(body) => {
332                hasher.update(body);
333                Ok(())
334            }
335            Self::Composite { parts, .. } => {
336                for part in parts.iter() {
337                    part.update_hash(hasher)?;
338                }
339                Ok(())
340            }
341            #[cfg(feature = "local")]
342            Self::Local {
343                root,
344                digest,
345                length,
346            } => crate::local::hash_body(root, digest, *length, hasher),
347        }
348    }
349
350    fn leaves(&self, output: &mut Vec<(BodyIdentity, usize)>) {
351        match self {
352            Self::Memory(body) => output.push((
353                BodyIdentity::Memory {
354                    address: body.as_ptr() as usize,
355                    length: body.len(),
356                },
357                body.len(),
358            )),
359            Self::Composite { parts, .. } => {
360                for part in parts.iter() {
361                    part.leaves(output);
362                }
363            }
364            #[cfg(feature = "local")]
365            Self::Local { digest, length, .. } => {
366                output.push((BodyIdentity::Local(*digest), *length));
367            }
368        }
369    }
370
371    async fn read_async(&self, start: usize, end: usize) -> Result<bytes::Bytes, ObjectsError> {
372        #[cfg(feature = "local")]
373        if self.has_local_storage() {
374            let body = self.clone();
375            return tokio::task::spawn_blocking(move || body.read(start, end))
376                .await
377                .map_err(|_| ObjectsError::Unavailable)?;
378        }
379        self.read(start, end)
380    }
381
382    #[cfg(feature = "local")]
383    fn has_local_storage(&self) -> bool {
384        match self {
385            Self::Memory(_) => false,
386            Self::Local { .. } => true,
387            Self::Composite { parts, .. } => parts.iter().any(Self::has_local_storage),
388        }
389    }
390}
391
392#[derive(Clone)]
393struct Version {
394    descriptor: wire::ObjectVersion,
395    body: Option<StoredBody>,
396}
397
398#[derive(Clone)]
399struct BucketState {
400    reference: wire::BucketRef,
401    created_at: prost_types::Timestamp,
402    objects: BTreeMap<String, Vec<Version>>,
403}
404
405#[derive(Clone)]
406struct SnapshotState {
407    reference: wire::SnapshotRef,
408    bucket: BucketState,
409}
410
411struct MultipartState {
412    bucket: wire::BucketRef,
413    object_key: String,
414    metadata: wire::ObjectMetadata,
415    condition: Option<Condition>,
416    parts: BTreeMap<u32, (wire::UploadedPart, StoredBody)>,
417}
418
419struct StoredPartRequest {
420    bucket: wire::BucketRef,
421    object_key: String,
422    upload_id: String,
423    part_number: u32,
424    body: StoredBody,
425    body_digest: [u8; 32],
426    idempotency_key: Option<String>,
427}
428
429#[cfg(feature = "local")]
430pub(crate) struct ExternalBody {
431    pub(crate) root: PathBuf,
432    pub(crate) digest: [u8; 32],
433    pub(crate) length: usize,
434}
435
436#[derive(Clone)]
437enum ListingItem {
438    Entry(Box<wire::ListEntry>),
439    Prefix(String),
440}
441
442struct ListingView {
443    binding: String,
444    items: Vec<ListingItem>,
445}
446
447#[derive(Clone)]
448enum MutationOutcome {
449    Bucket(Result<wire::Bucket, ObjectsError>),
450    Boolean(Result<bool, ObjectsError>),
451    Version(Result<wire::ObjectVersion, ObjectsError>),
452    Delete(Result<DeleteResult, ObjectsError>),
453    Snapshot(Result<wire::Snapshot, ObjectsError>),
454    Multipart(Result<wire::MultipartUpload, ObjectsError>),
455    Part(Result<wire::UploadedPart, ObjectsError>),
456}
457
458struct IdempotencyRecord {
459    fingerprint: blake3::Hash,
460    outcome: MutationOutcome,
461}
462
463struct State {
464    maximum_bytes: usize,
465    maximum_object_bytes: usize,
466    sequence: u64,
467    listing_sequence: u64,
468    bytes: usize,
469    body_references: BTreeMap<BodyIdentity, (usize, usize)>,
470    names: BTreeMap<String, String>,
471    buckets: BTreeMap<String, BucketState>,
472    snapshots: BTreeMap<String, SnapshotState>,
473    multiparts: BTreeMap<String, MultipartState>,
474    listings: BTreeMap<String, ListingView>,
475    idempotency: BTreeMap<String, IdempotencyRecord>,
476}
477
478impl Default for State {
479    fn default() -> Self {
480        Self {
481            maximum_bytes: MEMORY_BYTES,
482            maximum_object_bytes: MEMORY_BYTES,
483            sequence: 0,
484            listing_sequence: 0,
485            bytes: 0,
486            body_references: BTreeMap::new(),
487            names: BTreeMap::new(),
488            buckets: BTreeMap::new(),
489            snapshots: BTreeMap::new(),
490            multiparts: BTreeMap::new(),
491            listings: BTreeMap::new(),
492            idempotency: BTreeMap::new(),
493        }
494    }
495}
496
497impl State {
498    fn additional_body_bytes(&self, body: &StoredBody) -> Result<usize, ObjectsError> {
499        let mut leaves = Vec::new();
500        body.leaves(&mut leaves);
501        leaves
502            .into_iter()
503            .try_fold(0usize, |total, (identity, length)| {
504                if self.body_references.contains_key(&identity) {
505                    Ok(total)
506                } else {
507                    total.checked_add(length).ok_or(ObjectsError::Capacity)
508                }
509            })
510    }
511
512    fn retain_body(&mut self, body: &StoredBody) -> Result<(), ObjectsError> {
513        let mut leaves = Vec::new();
514        body.leaves(&mut leaves);
515        for (identity, length) in leaves {
516            match self.body_references.entry(identity) {
517                std::collections::btree_map::Entry::Vacant(entry) => {
518                    self.bytes = self
519                        .bytes
520                        .checked_add(length)
521                        .ok_or(ObjectsError::Capacity)?;
522                    entry.insert((length, 1));
523                }
524                std::collections::btree_map::Entry::Occupied(mut entry) => {
525                    if entry.get().0 != length {
526                        return Err(ObjectsError::Unavailable);
527                    }
528                    entry.get_mut().1 =
529                        entry.get().1.checked_add(1).ok_or(ObjectsError::Capacity)?;
530                }
531            }
532        }
533        Ok(())
534    }
535
536    fn release_body(&mut self, body: &StoredBody) -> Result<(), ObjectsError> {
537        let mut leaves = Vec::new();
538        body.leaves(&mut leaves);
539        for (identity, length) in leaves {
540            let references = self
541                .body_references
542                .get_mut(&identity)
543                .ok_or(ObjectsError::Unavailable)?;
544            if references.0 != length || references.1 == 0 {
545                return Err(ObjectsError::Unavailable);
546            }
547            references.1 -= 1;
548            if references.1 == 0 {
549                self.body_references.remove(&identity);
550                self.bytes = self
551                    .bytes
552                    .checked_sub(length)
553                    .ok_or(ObjectsError::Unavailable)?;
554            }
555        }
556        Ok(())
557    }
558
559    fn retain_bucket(&mut self, bucket: &BucketState) -> Result<(), ObjectsError> {
560        for body in bucket
561            .objects
562            .values()
563            .flatten()
564            .filter_map(|version| version.body.as_ref())
565        {
566            self.retain_body(body)?;
567        }
568        Ok(())
569    }
570
571    fn release_bucket(&mut self, bucket: &BucketState) -> Result<(), ObjectsError> {
572        for body in bucket
573            .objects
574            .values()
575            .flatten()
576            .filter_map(|version| version.body.as_ref())
577        {
578            self.release_body(body)?;
579        }
580        Ok(())
581    }
582}
583
584/// Bounded deterministic process-local reference provider.
585///
586/// Acknowledged state is ephemeral and has no isolation, durability, distribution, or production
587/// availability claim.
588#[derive(Clone, Default)]
589pub struct MemoryObjects {
590    state: Arc<Mutex<State>>,
591}
592
593impl MemoryObjects {
594    #[cfg(feature = "local")]
595    pub(crate) async fn local_body_digests(&self) -> BTreeSet<[u8; 32]> {
596        let state = self.state.lock().await;
597        state
598            .body_references
599            .keys()
600            .filter_map(|identity| match identity {
601                BodyIdentity::Local(digest) => Some(*digest),
602                BodyIdentity::Memory { .. } => None,
603            })
604            .collect()
605    }
606
607    /// Resolves a bucket by its canonical name without mutating provider state.
608    pub async fn bucket_named(&self, name: &str) -> Result<Option<wire::BucketRef>, ObjectsError> {
609        Self::validate_bucket_name(name)?;
610        let state = self.state.lock().await;
611        let Some(bucket_id) = state.names.get(name) else {
612            return Ok(None);
613        };
614        let bucket = state
615            .buckets
616            .get(bucket_id)
617            .ok_or(ObjectsError::Unavailable)?;
618        Ok(Some(bucket.reference.clone()))
619    }
620
621    /// Creates the default bounded reference provider with one deterministic bucket.
622    #[must_use]
623    pub fn with_default_bucket() -> (Self, wire::BucketRef) {
624        Self::from_bucket("memory-bucket".to_owned(), MEMORY_BYTES)
625    }
626
627    /// Creates an empty reference provider with an explicit aggregate byte ceiling.
628    ///
629    /// # Errors
630    ///
631    /// Rejects zero or process-unrepresentable limits before allocating provider state.
632    pub fn new(maximum_bytes: u64) -> Result<Self, ObjectsError> {
633        if maximum_bytes == 0 {
634            return Err(ObjectsError::Invalid("memory byte limit must be positive"));
635        }
636        Self::new_with_limits(maximum_bytes, maximum_bytes)
637    }
638
639    pub(crate) fn new_with_limits(
640        maximum_object_bytes: u64,
641        maximum_bytes: u64,
642    ) -> Result<Self, ObjectsError> {
643        let maximum_object_bytes = usize::try_from(maximum_object_bytes)
644            .map_err(|_| ObjectsError::Invalid("memory object limit is not representable"))?;
645        let maximum_bytes = usize::try_from(maximum_bytes)
646            .map_err(|_| ObjectsError::Invalid("memory byte limit is not representable"))?;
647        if maximum_object_bytes == 0 || maximum_bytes == 0 || maximum_object_bytes > maximum_bytes {
648            return Err(ObjectsError::Invalid("invalid memory object limits"));
649        }
650        Ok(Self {
651            state: Arc::new(Mutex::new(State {
652                maximum_bytes,
653                maximum_object_bytes,
654                ..State::default()
655            })),
656        })
657    }
658
659    /// Creates a bounded provider with one deterministic bucket already admitted.
660    ///
661    /// This synchronous constructor lets another in-process SDK family compose the exact public
662    /// reference provider without running an executor merely to establish its private bucket.
663    ///
664    /// # Errors
665    ///
666    /// Rejects an invalid bucket name or memory ceiling.
667    pub fn with_bucket(
668        name: impl Into<String>,
669        maximum_bytes: u64,
670    ) -> Result<(Self, wire::BucketRef), ObjectsError> {
671        let maximum_bytes = usize::try_from(maximum_bytes)
672            .map_err(|_| ObjectsError::Invalid("memory byte limit is not representable"))?;
673        if maximum_bytes == 0 {
674            return Err(ObjectsError::Invalid("memory byte limit must be positive"));
675        }
676        let name = name.into();
677        Self::validate_bucket_name(&name)?;
678        Ok(Self::from_bucket(name, maximum_bytes))
679    }
680
681    /// Creates one deterministic bucket with independent per-object and aggregate bounds.
682    ///
683    /// # Errors
684    ///
685    /// Rejects an invalid bucket name, zero limits, a per-object limit larger than the aggregate
686    /// limit, or limits that cannot be represented by this process.
687    pub fn with_bucket_limits(
688        name: impl Into<String>,
689        maximum_object_bytes: u64,
690        maximum_bytes: u64,
691    ) -> Result<(Self, wire::BucketRef), ObjectsError> {
692        let maximum_object_bytes = usize::try_from(maximum_object_bytes)
693            .map_err(|_| ObjectsError::Invalid("memory object limit is not representable"))?;
694        let maximum_bytes = usize::try_from(maximum_bytes)
695            .map_err(|_| ObjectsError::Invalid("memory byte limit is not representable"))?;
696        if maximum_object_bytes == 0 || maximum_bytes == 0 || maximum_object_bytes > maximum_bytes {
697            return Err(ObjectsError::Invalid("invalid memory object limits"));
698        }
699        let name = name.into();
700        Self::validate_bucket_name(&name)?;
701        Ok(Self::from_bucket_limits(
702            name,
703            maximum_object_bytes,
704            maximum_bytes,
705        ))
706    }
707
708    fn from_bucket(name: String, maximum_bytes: usize) -> (Self, wire::BucketRef) {
709        Self::from_bucket_limits(name, maximum_bytes, maximum_bytes)
710    }
711
712    fn from_bucket_limits(
713        name: String,
714        maximum_object_bytes: usize,
715        maximum_bytes: usize,
716    ) -> (Self, wire::BucketRef) {
717        let bucket_id = "bucket-0000000000000001".to_owned();
718        let reference = wire::BucketRef {
719            bucket_id: bucket_id.clone(),
720            name: name.clone(),
721        };
722        let mut state = State {
723            maximum_bytes,
724            maximum_object_bytes,
725            sequence: 1,
726            ..State::default()
727        };
728        state.names.insert(name, bucket_id.clone());
729        state.buckets.insert(
730            bucket_id,
731            BucketState {
732                reference: reference.clone(),
733                created_at: Self::timestamp(&state),
734                objects: BTreeMap::new(),
735            },
736        );
737        (
738            Self {
739                state: Arc::new(Mutex::new(state)),
740            },
741            reference,
742        )
743    }
744
745    fn fingerprint(kind: &str, fields: &[&[u8]]) -> blake3::Hash {
746        let mut hasher = blake3::Hasher::new();
747        for field in std::iter::once(kind.as_bytes()).chain(fields.iter().copied()) {
748            hasher.update(&(field.len() as u64).to_le_bytes());
749            hasher.update(field);
750        }
751        hasher.finalize()
752    }
753
754    fn execute<T: Clone>(
755        state: &mut State,
756        idempotency_key: Option<&str>,
757        fingerprint: blake3::Hash,
758        unpack: impl FnOnce(&MutationOutcome) -> Option<Result<T, ObjectsError>>,
759        pack: impl FnOnce(Result<T, ObjectsError>) -> MutationOutcome,
760        operation: impl FnOnce(&mut State) -> Result<T, ObjectsError>,
761    ) -> Result<T, ObjectsError> {
762        let Some(idempotency_key) = idempotency_key else {
763            return operation(state);
764        };
765        if idempotency_key.is_empty() || idempotency_key.len() > 256 {
766            return Err(ObjectsError::Invalid("invalid idempotency key"));
767        }
768        if let Some(record) = state.idempotency.get(idempotency_key) {
769            if record.fingerprint != fingerprint {
770                return Err(ObjectsError::IdempotencyMismatch);
771            }
772            return unpack(&record.outcome).ok_or(ObjectsError::IdempotencyMismatch)?;
773        }
774        let outcome = operation(state);
775        state.idempotency.insert(
776            idempotency_key.to_owned(),
777            IdempotencyRecord {
778                fingerprint,
779                outcome: pack(outcome.clone()),
780            },
781        );
782        outcome
783    }
784
785    fn reference_fields(reference: &wire::BucketRef) -> [&[u8]; 2] {
786        [reference.bucket_id.as_bytes(), reference.name.as_bytes()]
787    }
788
789    fn condition_bytes(condition: &Option<Condition>) -> Vec<u8> {
790        match condition {
791            None => b"none".to_vec(),
792            Some(Condition::IfAbsent) => b"absent".to_vec(),
793            Some(Condition::IfMatch(value)) => [b"etag:".as_slice(), value.as_bytes()].concat(),
794            Some(Condition::IfVersion(value)) => {
795                [b"version:".as_slice(), value.as_bytes()].concat()
796            }
797        }
798    }
799
800    fn metadata_bytes(metadata: &wire::ObjectMetadata) -> Vec<u8> {
801        let mut fields = vec![
802            metadata.content_type.as_str(),
803            metadata.content_encoding.as_str(),
804            metadata.cache_control.as_str(),
805            metadata.content_disposition.as_str(),
806            metadata.content_language.as_str(),
807        ];
808        fields.push(if metadata.expires_unix_seconds.is_some() {
809            "expires"
810        } else {
811            "no-expires"
812        });
813        let mut encoded = Vec::new();
814        for field in fields {
815            encoded.extend_from_slice(&(field.len() as u64).to_le_bytes());
816            encoded.extend_from_slice(field.as_bytes());
817        }
818        if let Some(expires) = metadata.expires_unix_seconds {
819            encoded.extend_from_slice(&expires.to_le_bytes());
820        }
821        let mut user = metadata.user.iter().collect::<Vec<_>>();
822        user.sort_unstable_by(|left, right| left.0.cmp(right.0));
823        for (name, value) in user {
824            for field in [name.as_bytes(), value.as_bytes()] {
825                encoded.extend_from_slice(&(field.len() as u64).to_le_bytes());
826                encoded.extend_from_slice(field);
827            }
828        }
829        encoded
830    }
831
832    fn next(state: &mut State, kind: &str) -> Result<String, ObjectsError> {
833        let sequence = if kind == "listing" {
834            &mut state.listing_sequence
835        } else {
836            &mut state.sequence
837        };
838        *sequence = sequence.checked_add(1).ok_or(ObjectsError::Capacity)?;
839        Ok(format!("{kind}-{sequence:016x}"))
840    }
841
842    fn timestamp(state: &State) -> prost_types::Timestamp {
843        prost_types::Timestamp {
844            seconds: i64::try_from(state.sequence).unwrap_or(i64::MAX),
845            nanos: 0,
846        }
847    }
848
849    fn bucket<'a>(
850        state: &'a State,
851        reference: &wire::BucketRef,
852    ) -> Result<&'a BucketState, ObjectsError> {
853        state
854            .buckets
855            .get(&reference.bucket_id)
856            .filter(|bucket| bucket.reference == *reference)
857            .ok_or(ObjectsError::NotFound)
858    }
859
860    fn bucket_mut<'a>(
861        state: &'a mut State,
862        reference: &wire::BucketRef,
863    ) -> Result<&'a mut BucketState, ObjectsError> {
864        state
865            .buckets
866            .get_mut(&reference.bucket_id)
867            .filter(|bucket| bucket.reference == *reference)
868            .ok_or(ObjectsError::NotFound)
869    }
870
871    fn validate_key(value: &str) -> Result<(), ObjectsError> {
872        if value.is_empty() || value.len() > limits::KEY_BYTES || value.contains('\0') {
873            Err(ObjectsError::Invalid("invalid object key"))
874        } else {
875            Ok(())
876        }
877    }
878
879    fn validate_bucket_name(name: &str) -> Result<(), ObjectsError> {
880        let valid_edges = name
881            .as_bytes()
882            .first()
883            .zip(name.as_bytes().last())
884            .is_some_and(|(first, last)| {
885                first.is_ascii_alphanumeric() && last.is_ascii_alphanumeric()
886            });
887        let valid_body = name
888            .bytes()
889            .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-');
890        if !(3..=63).contains(&name.len()) || !valid_edges || !valid_body {
891            Err(ObjectsError::Invalid("invalid bucket name"))
892        } else {
893            Ok(())
894        }
895    }
896
897    fn validate_metadata(metadata: &wire::ObjectMetadata) -> Result<(), ObjectsError> {
898        let fixed = [
899            &metadata.content_type,
900            &metadata.content_encoding,
901            &metadata.cache_control,
902            &metadata.content_disposition,
903            &metadata.content_language,
904        ]
905        .into_iter()
906        .map(String::len)
907        .sum::<usize>();
908        let user = metadata
909            .user
910            .iter()
911            .map(|(name, value)| name.len() + value.len())
912            .sum::<usize>();
913        if fixed > SYSTEM_METADATA_BYTES
914            || user > limits::USER_METADATA_BYTES
915            || metadata.user.iter().any(|(name, value)| {
916                !name.bytes().all(|byte| {
917                    byte.is_ascii_alphanumeric()
918                        || matches!(
919                            byte,
920                            b'!' | b'#'
921                                | b'$'
922                                | b'%'
923                                | b'&'
924                                | b'\''
925                                | b'*'
926                                | b'+'
927                                | b'-'
928                                | b'.'
929                                | b'^'
930                                | b'_'
931                                | b'`'
932                                | b'|'
933                                | b'~'
934                        )
935                }) || name.is_empty()
936                    || !Self::valid_header_value(value)
937            })
938            || [
939                &metadata.content_type,
940                &metadata.content_encoding,
941                &metadata.cache_control,
942                &metadata.content_disposition,
943                &metadata.content_language,
944            ]
945            .into_iter()
946            .any(|value| !Self::valid_header_value(value))
947            || metadata
948                .expires_unix_seconds
949                .is_some_and(|seconds| !(-62_167_219_200..=253_402_300_799).contains(&seconds))
950        {
951            Err(ObjectsError::Invalid("invalid object metadata"))
952        } else {
953            Ok(())
954        }
955    }
956
957    fn valid_header_value(value: &str) -> bool {
958        value
959            .bytes()
960            .all(|byte| byte == b'\t' || (0x20..=0x7e).contains(&byte))
961    }
962
963    fn target(state: &State, target: &ReadTarget) -> Result<BucketState, ObjectsError> {
964        match target {
965            ReadTarget::Bucket(reference) => Ok(Self::bucket(state, reference)?.clone()),
966            ReadTarget::Snapshot(reference) => state
967                .snapshots
968                .get(&reference.snapshot_id)
969                .filter(|snapshot| snapshot.reference == *reference)
970                .map(|snapshot| snapshot.bucket.clone())
971                .ok_or(ObjectsError::NotFound),
972        }
973    }
974
975    fn target_ref<'a>(
976        state: &'a State,
977        target: &ReadTarget,
978    ) -> Result<&'a BucketState, ObjectsError> {
979        match target {
980            ReadTarget::Bucket(reference) => Self::bucket(state, reference),
981            ReadTarget::Snapshot(reference) => state
982                .snapshots
983                .get(&reference.snapshot_id)
984                .filter(|snapshot| snapshot.reference == *reference)
985                .map(|snapshot| &snapshot.bucket)
986                .ok_or(ObjectsError::NotFound),
987        }
988    }
989
990    fn visible<'a>(
991        bucket: &'a BucketState,
992        object_key: &str,
993        version_id: Option<&str>,
994    ) -> Result<&'a Version, ObjectsError> {
995        let versions = bucket
996            .objects
997            .get(object_key)
998            .ok_or(ObjectsError::NotFound)?;
999        let version = match version_id {
1000            Some(identity) => versions
1001                .iter()
1002                .rev()
1003                .find(|version| version.descriptor.version_id == identity),
1004            None => versions.last(),
1005        }
1006        .ok_or(ObjectsError::NotFound)?;
1007        if version.descriptor.delete_marker {
1008            Err(ObjectsError::NotFound)
1009        } else {
1010            Ok(version)
1011        }
1012    }
1013
1014    fn condition(current: Option<&Version>, condition: &Option<Condition>) -> bool {
1015        match condition {
1016            None => true,
1017            Some(Condition::IfAbsent) => current.is_none_or(|value| value.descriptor.delete_marker),
1018            Some(Condition::IfMatch(etag)) => current.is_some_and(|value| {
1019                !value.descriptor.delete_marker && value.descriptor.etag == *etag
1020            }),
1021            Some(Condition::IfVersion(identity)) => {
1022                current.is_some_and(|value| value.descriptor.version_id == *identity)
1023            }
1024        }
1025    }
1026
1027    fn descriptor(
1028        state: &mut State,
1029        body_digest: &[u8; 32],
1030        body_length: usize,
1031        metadata: wire::ObjectMetadata,
1032        marker: bool,
1033    ) -> Result<wire::ObjectVersion, ObjectsError> {
1034        let version_id = Self::next(state, "version")?;
1035        let etag = if marker {
1036            String::new()
1037        } else {
1038            format!("\"{}\"", blake3::Hash::from_bytes(*body_digest).to_hex())
1039        };
1040        Ok(wire::ObjectVersion {
1041            version_id,
1042            etag,
1043            size: body_length as u64,
1044            delete_marker: marker,
1045            metadata: Some(metadata),
1046            created_at: Some(Self::timestamp(state)),
1047        })
1048    }
1049
1050    fn binding(
1051        target: &ReadTarget,
1052        prefix: &str,
1053        delimiter: Option<&str>,
1054        versions: bool,
1055    ) -> String {
1056        let target = match target {
1057            ReadTarget::Bucket(value) => format!("b:{}:{}", value.bucket_id, value.name),
1058            ReadTarget::Snapshot(value) => {
1059                format!("s:{}:{}", value.snapshot_id, value.source_bucket_id)
1060            }
1061        };
1062        format!(
1063            "{target}|{prefix}|{}|{versions}",
1064            delimiter.unwrap_or_default()
1065        )
1066    }
1067
1068    fn listing_items(
1069        bucket: &BucketState,
1070        prefix: &str,
1071        delimiter: Option<&str>,
1072        versions: bool,
1073    ) -> Vec<ListingItem> {
1074        let mut items = Vec::new();
1075        let mut prefixes = BTreeSet::new();
1076        for (object_key, history) in bucket.objects.range(prefix.to_owned()..) {
1077            if !object_key.starts_with(prefix) {
1078                break;
1079            }
1080            if let Some(delimiter) = delimiter
1081                && let Some(position) = object_key[prefix.len()..].find(delimiter)
1082            {
1083                let end = prefix.len() + position + delimiter.len();
1084                prefixes.insert(object_key[..end].to_owned());
1085                continue;
1086            }
1087            if versions {
1088                items.extend(history.iter().rev().map(|version| {
1089                    ListingItem::Entry(Box::new(wire::ListEntry {
1090                        object_key: object_key.clone(),
1091                        version: Some(version.descriptor.clone()),
1092                    }))
1093                }));
1094            } else if let Some(version) = history.last()
1095                && !version.descriptor.delete_marker
1096            {
1097                items.push(ListingItem::Entry(Box::new(wire::ListEntry {
1098                    object_key: object_key.clone(),
1099                    version: Some(version.descriptor.clone()),
1100                })));
1101            }
1102        }
1103        items.extend(prefixes.into_iter().map(ListingItem::Prefix));
1104        items.sort_by(|left, right| {
1105            let left = match left {
1106                ListingItem::Entry(value) => &value.object_key,
1107                ListingItem::Prefix(value) => value,
1108            };
1109            let right = match right {
1110                ListingItem::Entry(value) => &value.object_key,
1111                ListingItem::Prefix(value) => value,
1112            };
1113            left.cmp(right)
1114        });
1115        items
1116    }
1117
1118    async fn put_stored(
1119        &self,
1120        request: PutRequest,
1121        body: StoredBody,
1122        body_digest: [u8; 32],
1123    ) -> Result<wire::ObjectVersion, ObjectsError> {
1124        let PutRequest {
1125            bucket,
1126            object_key,
1127            body: _,
1128            metadata,
1129            condition,
1130            idempotency_key,
1131        } = request;
1132        Self::validate_key(&object_key)?;
1133        Self::validate_metadata(&metadata)?;
1134        let bucket_fields = Self::reference_fields(&bucket);
1135        let condition_bytes = Self::condition_bytes(&condition);
1136        let metadata_bytes = Self::metadata_bytes(&metadata);
1137        let fingerprint = Self::fingerprint(
1138            "put",
1139            &[
1140                bucket_fields[0],
1141                bucket_fields[1],
1142                object_key.as_bytes(),
1143                &condition_bytes,
1144                &metadata_bytes,
1145                &body_digest,
1146            ],
1147        );
1148        let mut state = self.state.lock().await;
1149        Self::execute(
1150            &mut state,
1151            idempotency_key.as_deref(),
1152            fingerprint,
1153            |outcome| match outcome {
1154                MutationOutcome::Version(value) => Some(value.clone()),
1155                _ => None,
1156            },
1157            MutationOutcome::Version,
1158            |state| {
1159                let current = Self::bucket(state, &bucket)?
1160                    .objects
1161                    .get(&object_key)
1162                    .and_then(|history| history.last())
1163                    .cloned();
1164                if !Self::condition(current.as_ref(), &condition) {
1165                    return Err(ObjectsError::PreconditionFailed);
1166                }
1167                let size = body.len();
1168                let additional = state.additional_body_bytes(&body)?;
1169                if size > state.maximum_object_bytes
1170                    || state
1171                        .bytes
1172                        .checked_add(additional)
1173                        .is_none_or(|bytes| bytes > state.maximum_bytes)
1174                {
1175                    return Err(ObjectsError::Capacity);
1176                }
1177                let descriptor = Self::descriptor(state, &body_digest, size, metadata, false)?;
1178                state.retain_body(&body)?;
1179                Self::bucket_mut(state, &bucket)?
1180                    .objects
1181                    .entry(object_key)
1182                    .or_default()
1183                    .push(Version {
1184                        descriptor: descriptor.clone(),
1185                        body: Some(body),
1186                    });
1187                Ok(descriptor)
1188            },
1189        )
1190    }
1191
1192    #[cfg(feature = "local")]
1193    pub(crate) async fn put_external(
1194        &self,
1195        request: PutRequest,
1196        external: ExternalBody,
1197    ) -> Result<wire::ObjectVersion, ObjectsError> {
1198        let ExternalBody {
1199            root,
1200            digest,
1201            length,
1202        } = external;
1203        self.put_stored(
1204            request,
1205            StoredBody::Local {
1206                root: Arc::new(root),
1207                digest,
1208                length,
1209            },
1210            digest,
1211        )
1212        .await
1213    }
1214
1215    async fn upload_part_stored(
1216        &self,
1217        request: StoredPartRequest,
1218    ) -> Result<wire::UploadedPart, ObjectsError> {
1219        let StoredPartRequest {
1220            bucket,
1221            object_key,
1222            upload_id,
1223            part_number,
1224            body,
1225            body_digest,
1226            idempotency_key,
1227        } = request;
1228        if !(1..=limits::MULTIPART_PARTS).contains(&part_number)
1229            || body.len() as u64 > limits::MAX_MULTIPART_PART_BYTES
1230        {
1231            return Err(ObjectsError::Invalid("invalid multipart part"));
1232        }
1233        let bucket_fields = Self::reference_fields(&bucket);
1234        let part_number_bytes = part_number.to_le_bytes();
1235        let fingerprint = Self::fingerprint(
1236            "upload-part",
1237            &[
1238                bucket_fields[0],
1239                bucket_fields[1],
1240                object_key.as_bytes(),
1241                upload_id.as_bytes(),
1242                &part_number_bytes,
1243                &body_digest,
1244            ],
1245        );
1246        let mut state = self.state.lock().await;
1247        Self::execute(
1248            &mut state,
1249            idempotency_key.as_deref(),
1250            fingerprint,
1251            |outcome| match outcome {
1252                MutationOutcome::Part(value) => Some(value.clone()),
1253                _ => None,
1254            },
1255            MutationOutcome::Part,
1256            |state| {
1257                let existing_body = state
1258                    .multiparts
1259                    .get(&upload_id)
1260                    .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1261                    .ok_or(ObjectsError::NotFound)?
1262                    .parts
1263                    .get(&part_number)
1264                    .map(|(_, body)| body.clone());
1265                if let Some(existing) = &existing_body {
1266                    state.release_body(existing)?;
1267                }
1268                let additional = state.additional_body_bytes(&body)?;
1269                if state
1270                    .bytes
1271                    .checked_add(additional)
1272                    .is_none_or(|bytes| bytes > state.maximum_bytes)
1273                {
1274                    if let Some(existing) = &existing_body {
1275                        state.retain_body(existing)?;
1276                    }
1277                    return Err(ObjectsError::Capacity);
1278                }
1279                let part = wire::UploadedPart {
1280                    part_number,
1281                    etag: format!("\"{}\"", blake3::Hash::from_bytes(body_digest).to_hex()),
1282                    size: body.len() as u64,
1283                };
1284                state
1285                    .multiparts
1286                    .get_mut(&upload_id)
1287                    .ok_or(ObjectsError::NotFound)?
1288                    .parts
1289                    .insert(part_number, (part.clone(), body));
1290                let admitted = state
1291                    .multiparts
1292                    .get(&upload_id)
1293                    .and_then(|upload| upload.parts.get(&part_number))
1294                    .map(|(_, body)| body.clone())
1295                    .ok_or(ObjectsError::Unavailable)?;
1296                state.retain_body(&admitted)?;
1297                Ok(part)
1298            },
1299        )
1300    }
1301
1302    #[cfg(feature = "local")]
1303    pub(crate) async fn upload_part_external(
1304        &self,
1305        bucket: wire::BucketRef,
1306        object_key: String,
1307        upload_id: String,
1308        part_number: u32,
1309        external: ExternalBody,
1310        idempotency_key: Option<String>,
1311    ) -> Result<wire::UploadedPart, ObjectsError> {
1312        let ExternalBody {
1313            root,
1314            digest,
1315            length,
1316        } = external;
1317        self.upload_part_stored(StoredPartRequest {
1318            bucket,
1319            object_key,
1320            upload_id,
1321            part_number,
1322            body: StoredBody::Local {
1323                root: Arc::new(root),
1324                digest,
1325                length,
1326            },
1327            body_digest: digest,
1328            idempotency_key,
1329        })
1330        .await
1331    }
1332}
1333
1334#[async_trait]
1335impl ObjectsProvider for MemoryObjects {
1336    async fn create_bucket(
1337        &self,
1338        name: String,
1339        idempotency_key: Option<String>,
1340    ) -> Result<wire::Bucket, ObjectsError> {
1341        Self::validate_bucket_name(&name)?;
1342        let fingerprint = Self::fingerprint("create-bucket", &[name.as_bytes()]);
1343        let mut state = self.state.lock().await;
1344        Self::execute(
1345            &mut state,
1346            idempotency_key.as_deref(),
1347            fingerprint,
1348            |outcome| match outcome {
1349                MutationOutcome::Bucket(value) => Some(value.clone()),
1350                _ => None,
1351            },
1352            MutationOutcome::Bucket,
1353            |state| {
1354                if state.names.contains_key(&name) {
1355                    return Err(ObjectsError::AlreadyExists);
1356                }
1357                let bucket_id = Self::next(state, "bucket")?;
1358                let reference = wire::BucketRef {
1359                    bucket_id: bucket_id.clone(),
1360                    name: name.clone(),
1361                };
1362                let bucket = BucketState {
1363                    reference: reference.clone(),
1364                    created_at: Self::timestamp(state),
1365                    objects: BTreeMap::new(),
1366                };
1367                state.names.insert(name, bucket_id.clone());
1368                state.buckets.insert(bucket_id, bucket.clone());
1369                Ok(wire::Bucket {
1370                    bucket: Some(reference),
1371                    created_at: Some(bucket.created_at),
1372                })
1373            },
1374        )
1375    }
1376
1377    async fn head_bucket(&self, bucket: &wire::BucketRef) -> Result<wire::Bucket, ObjectsError> {
1378        let state = self.state.lock().await;
1379        let bucket = Self::bucket(&state, bucket)?;
1380        Ok(wire::Bucket {
1381            bucket: Some(bucket.reference.clone()),
1382            created_at: Some(bucket.created_at),
1383        })
1384    }
1385
1386    async fn delete_bucket(
1387        &self,
1388        bucket: &wire::BucketRef,
1389        idempotency_key: Option<String>,
1390    ) -> Result<bool, ObjectsError> {
1391        let fields = Self::reference_fields(bucket);
1392        let fingerprint = Self::fingerprint("delete-bucket", &fields);
1393        let mut state = self.state.lock().await;
1394        Self::execute(
1395            &mut state,
1396            idempotency_key.as_deref(),
1397            fingerprint,
1398            |outcome| match outcome {
1399                MutationOutcome::Boolean(value) => Some(value.clone()),
1400                _ => None,
1401            },
1402            MutationOutcome::Boolean,
1403            |state| {
1404                let existing = Self::bucket(state, bucket)?.clone();
1405                if existing.objects.values().any(|history| !history.is_empty())
1406                    || state
1407                        .multiparts
1408                        .values()
1409                        .any(|upload| upload.bucket == *bucket)
1410                {
1411                    return Err(ObjectsError::PreconditionFailed);
1412                }
1413                state.buckets.remove(&bucket.bucket_id);
1414                state.names.remove(&bucket.name);
1415                Ok(true)
1416            },
1417        )
1418    }
1419
1420    async fn put(&self, request: PutRequest) -> Result<wire::ObjectVersion, ObjectsError> {
1421        let digest = *blake3::hash(&request.body).as_bytes();
1422        let body = StoredBody::memory(request.body.clone());
1423        self.put_stored(request, body, digest).await
1424    }
1425
1426    async fn get(&self, request: GetRequest) -> Result<BufferedObject, ObjectsError> {
1427        Self::validate_key(&request.object_key)?;
1428        let state = self.state.lock().await;
1429        let bucket = Self::target_ref(&state, &request.target)?;
1430        let version = Self::visible(bucket, &request.object_key, request.version_id.as_deref())?;
1431        if request
1432            .if_match
1433            .as_ref()
1434            .is_some_and(|etag| *etag != version.descriptor.etag)
1435            || request
1436                .if_none_match
1437                .as_ref()
1438                .is_some_and(|etag| *etag == version.descriptor.etag)
1439        {
1440            return Err(ObjectsError::PreconditionFailed);
1441        }
1442        let descriptor = version.descriptor.clone();
1443        let body = version.body.clone().ok_or(ObjectsError::NotFound)?;
1444        drop(state);
1445        let (start, end) = match request.range {
1446            None => (0, body.len()),
1447            Some((start, end)) => {
1448                let start =
1449                    usize::try_from(start).map_err(|_| ObjectsError::Invalid("invalid range"))?;
1450                let end = usize::try_from(end.unwrap_or(body.len().saturating_sub(1) as u64))
1451                    .map_err(|_| ObjectsError::Invalid("invalid range"))?;
1452                if start > end || end >= body.len() {
1453                    return Err(ObjectsError::Invalid("invalid range"));
1454                }
1455                (start, end.saturating_add(1))
1456            }
1457        };
1458        if end.saturating_sub(start) as u64 > request.maximum_bytes {
1459            return Err(ObjectsError::Capacity);
1460        }
1461        let selected = body.read_async(start, end).await?;
1462        Ok(BufferedObject {
1463            version: descriptor,
1464            body: selected,
1465        })
1466    }
1467
1468    async fn delete(
1469        &self,
1470        bucket: wire::BucketRef,
1471        object_key: String,
1472        version_id: Option<String>,
1473        condition: Option<Condition>,
1474        idempotency_key: Option<String>,
1475    ) -> Result<DeleteResult, ObjectsError> {
1476        Self::validate_key(&object_key)?;
1477        let bucket_fields = Self::reference_fields(&bucket);
1478        let condition_bytes = Self::condition_bytes(&condition);
1479        let fingerprint = Self::fingerprint(
1480            "delete",
1481            &[
1482                bucket_fields[0],
1483                bucket_fields[1],
1484                object_key.as_bytes(),
1485                version_id.as_deref().unwrap_or_default().as_bytes(),
1486                &condition_bytes,
1487            ],
1488        );
1489        let mut state = self.state.lock().await;
1490        Self::execute(
1491            &mut state,
1492            idempotency_key.as_deref(),
1493            fingerprint,
1494            |outcome| match outcome {
1495                MutationOutcome::Delete(value) => Some(value.clone()),
1496                _ => None,
1497            },
1498            MutationOutcome::Delete,
1499            |state| {
1500                let current = Self::bucket(state, &bucket)?
1501                    .objects
1502                    .get(&object_key)
1503                    .and_then(|history| history.last())
1504                    .cloned();
1505                if !Self::condition(current.as_ref(), &condition) {
1506                    return Err(ObjectsError::PreconditionFailed);
1507                }
1508                if let Some(identity) = version_id {
1509                    let (removed_body, empty) = {
1510                        let Some(history) = Self::bucket_mut(state, &bucket)?
1511                            .objects
1512                            .get_mut(&object_key)
1513                        else {
1514                            return Ok(DeleteResult {
1515                                existed: false,
1516                                marker: None,
1517                            });
1518                        };
1519                        let Some(position) = history
1520                            .iter()
1521                            .position(|version| version.descriptor.version_id == identity)
1522                        else {
1523                            return Ok(DeleteResult {
1524                                existed: false,
1525                                marker: None,
1526                            });
1527                        };
1528                        let removed = history.remove(position);
1529                        (removed.body, history.is_empty())
1530                    };
1531                    if let Some(body) = removed_body {
1532                        state.release_body(&body)?;
1533                    }
1534                    if empty {
1535                        Self::bucket_mut(state, &bucket)?
1536                            .objects
1537                            .remove(&object_key);
1538                    }
1539                    Ok(DeleteResult {
1540                        existed: true,
1541                        marker: None,
1542                    })
1543                } else {
1544                    let marker = Self::descriptor(
1545                        state,
1546                        blake3::hash(&[]).as_bytes(),
1547                        0,
1548                        wire::ObjectMetadata::default(),
1549                        true,
1550                    )?;
1551                    Self::bucket_mut(state, &bucket)?
1552                        .objects
1553                        .entry(object_key)
1554                        .or_default()
1555                        .push(Version {
1556                            descriptor: marker.clone(),
1557                            body: None,
1558                        });
1559                    Ok(DeleteResult {
1560                        existed: true,
1561                        marker: Some(marker),
1562                    })
1563                }
1564            },
1565        )
1566    }
1567
1568    async fn list(
1569        &self,
1570        target: ReadTarget,
1571        prefix: String,
1572        delimiter: Option<String>,
1573        versions: bool,
1574        page_size: u32,
1575        continuation: Option<String>,
1576    ) -> Result<ProviderListPage, ObjectsError> {
1577        if page_size == 0
1578            || page_size > limits::LIST_PAGE_ENTRIES
1579            || delimiter.as_ref().is_some_and(String::is_empty)
1580        {
1581            return Err(ObjectsError::Invalid("invalid listing"));
1582        }
1583        let mut state = self.state.lock().await;
1584        let binding = Self::binding(&target, &prefix, delimiter.as_deref(), versions);
1585        let (view_id, offset) = if let Some(token) = continuation {
1586            let (view, offset) = token
1587                .rsplit_once(':')
1588                .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1589            let offset = offset
1590                .parse::<usize>()
1591                .map_err(|_| ObjectsError::Invalid("invalid continuation"))?;
1592            (view.to_owned(), offset)
1593        } else {
1594            let bucket = Self::target(&state, &target)?;
1595            let view_id = Self::next(&mut state, "listing")?;
1596            state.listings.insert(
1597                view_id.clone(),
1598                ListingView {
1599                    binding: binding.clone(),
1600                    items: Self::listing_items(&bucket, &prefix, delimiter.as_deref(), versions),
1601                },
1602            );
1603            (view_id, 0)
1604        };
1605        let view = state
1606            .listings
1607            .get(&view_id)
1608            .filter(|view| view.binding == binding)
1609            .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1610        if offset > view.items.len() {
1611            return Err(ObjectsError::Invalid("invalid continuation"));
1612        }
1613        let end = offset
1614            .saturating_add(page_size as usize)
1615            .min(view.items.len());
1616        let mut entries = Vec::new();
1617        let mut common_prefixes = Vec::new();
1618        for item in &view.items[offset..end] {
1619            match item {
1620                ListingItem::Entry(value) => entries.push(value.as_ref().clone()),
1621                ListingItem::Prefix(value) => common_prefixes.push(value.clone()),
1622            }
1623        }
1624        Ok(ProviderListPage {
1625            entries,
1626            common_prefixes,
1627            continuation: (end < view.items.len()).then(|| format!("{view_id}:{end}")),
1628        })
1629    }
1630
1631    async fn snapshot(
1632        &self,
1633        bucket: wire::BucketRef,
1634        idempotency_key: Option<String>,
1635    ) -> Result<wire::Snapshot, ObjectsError> {
1636        let fields = Self::reference_fields(&bucket);
1637        let fingerprint = Self::fingerprint("snapshot", &fields);
1638        let mut state = self.state.lock().await;
1639        Self::execute(
1640            &mut state,
1641            idempotency_key.as_deref(),
1642            fingerprint,
1643            |outcome| match outcome {
1644                MutationOutcome::Snapshot(value) => Some(value.clone()),
1645                _ => None,
1646            },
1647            MutationOutcome::Snapshot,
1648            |state| {
1649                let source = Self::bucket(state, &bucket)?.clone();
1650                state.retain_bucket(&source)?;
1651                let snapshot_id = Self::next(state, "snapshot")?;
1652                let reference = wire::SnapshotRef {
1653                    snapshot_id: snapshot_id.clone(),
1654                    source_bucket_id: bucket.bucket_id,
1655                };
1656                let created_at = Self::timestamp(state);
1657                state.snapshots.insert(
1658                    snapshot_id,
1659                    SnapshotState {
1660                        reference: reference.clone(),
1661                        bucket: source,
1662                    },
1663                );
1664                Ok(wire::Snapshot {
1665                    snapshot: Some(reference),
1666                    created_at: Some(created_at),
1667                })
1668            },
1669        )
1670    }
1671
1672    async fn destroy_snapshot(
1673        &self,
1674        snapshot: wire::SnapshotRef,
1675        idempotency_key: Option<String>,
1676    ) -> Result<bool, ObjectsError> {
1677        let fingerprint = Self::fingerprint(
1678            "destroy-snapshot",
1679            &[
1680                snapshot.snapshot_id.as_bytes(),
1681                snapshot.source_bucket_id.as_bytes(),
1682            ],
1683        );
1684        let mut state = self.state.lock().await;
1685        Self::execute(
1686            &mut state,
1687            idempotency_key.as_deref(),
1688            fingerprint,
1689            |outcome| match outcome {
1690                MutationOutcome::Boolean(value) => Some(value.clone()),
1691                _ => None,
1692            },
1693            MutationOutcome::Boolean,
1694            |state| {
1695                let exists = state
1696                    .snapshots
1697                    .get(&snapshot.snapshot_id)
1698                    .is_some_and(|value| value.reference == snapshot);
1699                if !exists {
1700                    return Ok(false);
1701                }
1702                let removed = state
1703                    .snapshots
1704                    .remove(&snapshot.snapshot_id)
1705                    .ok_or(ObjectsError::Unavailable)?;
1706                state.release_bucket(&removed.bucket)?;
1707                Ok(true)
1708            },
1709        )
1710    }
1711
1712    async fn fork(
1713        &self,
1714        source: ReadTarget,
1715        destination_name: String,
1716        idempotency_key: Option<String>,
1717    ) -> Result<wire::Bucket, ObjectsError> {
1718        let source_identity = match &source {
1719            ReadTarget::Bucket(value) => format!("bucket:{}:{}", value.bucket_id, value.name),
1720            ReadTarget::Snapshot(value) => {
1721                format!("snapshot:{}:{}", value.snapshot_id, value.source_bucket_id)
1722            }
1723        };
1724        let fingerprint = Self::fingerprint(
1725            "fork",
1726            &[source_identity.as_bytes(), destination_name.as_bytes()],
1727        );
1728        let mut state = self.state.lock().await;
1729        Self::execute(
1730            &mut state,
1731            idempotency_key.as_deref(),
1732            fingerprint,
1733            |outcome| match outcome {
1734                MutationOutcome::Bucket(value) => Some(value.clone()),
1735                _ => None,
1736            },
1737            MutationOutcome::Bucket,
1738            |state| {
1739                if state.names.contains_key(&destination_name) {
1740                    return Err(ObjectsError::AlreadyExists);
1741                }
1742                let source = Self::target(state, &source)?;
1743                state.retain_bucket(&source)?;
1744                let bucket_id = Self::next(state, "bucket")?;
1745                let reference = wire::BucketRef {
1746                    bucket_id: bucket_id.clone(),
1747                    name: destination_name.clone(),
1748                };
1749                let created_at = Self::timestamp(state);
1750                state.names.insert(destination_name, bucket_id.clone());
1751                state.buckets.insert(
1752                    bucket_id,
1753                    BucketState {
1754                        reference: reference.clone(),
1755                        created_at,
1756                        objects: source.objects,
1757                    },
1758                );
1759                Ok(wire::Bucket {
1760                    bucket: Some(reference),
1761                    created_at: Some(created_at),
1762                })
1763            },
1764        )
1765    }
1766
1767    async fn create_multipart(
1768        &self,
1769        bucket: wire::BucketRef,
1770        object_key: String,
1771        metadata: wire::ObjectMetadata,
1772        condition: Option<Condition>,
1773        idempotency_key: Option<String>,
1774    ) -> Result<wire::MultipartUpload, ObjectsError> {
1775        Self::validate_key(&object_key)?;
1776        Self::validate_metadata(&metadata)?;
1777        let bucket_fields = Self::reference_fields(&bucket);
1778        let metadata_bytes = Self::metadata_bytes(&metadata);
1779        let condition_bytes = Self::condition_bytes(&condition);
1780        let fingerprint = Self::fingerprint(
1781            "create-multipart",
1782            &[
1783                bucket_fields[0],
1784                bucket_fields[1],
1785                object_key.as_bytes(),
1786                &metadata_bytes,
1787                &condition_bytes,
1788            ],
1789        );
1790        let mut state = self.state.lock().await;
1791        Self::execute(
1792            &mut state,
1793            idempotency_key.as_deref(),
1794            fingerprint,
1795            |outcome| match outcome {
1796                MutationOutcome::Multipart(value) => Some(value.clone()),
1797                _ => None,
1798            },
1799            MutationOutcome::Multipart,
1800            |state| {
1801                Self::bucket(state, &bucket)?;
1802                let upload_id = Self::next(state, "upload")?;
1803                state.multiparts.insert(
1804                    upload_id.clone(),
1805                    MultipartState {
1806                        bucket,
1807                        object_key,
1808                        metadata,
1809                        condition,
1810                        parts: BTreeMap::new(),
1811                    },
1812                );
1813                Ok(wire::MultipartUpload { upload_id })
1814            },
1815        )
1816    }
1817
1818    async fn upload_part(
1819        &self,
1820        bucket: wire::BucketRef,
1821        object_key: String,
1822        upload_id: String,
1823        part_number: u32,
1824        body: bytes::Bytes,
1825        idempotency_key: Option<String>,
1826    ) -> Result<wire::UploadedPart, ObjectsError> {
1827        let digest = *blake3::hash(&body).as_bytes();
1828        self.upload_part_stored(StoredPartRequest {
1829            bucket,
1830            object_key,
1831            upload_id,
1832            part_number,
1833            body: StoredBody::memory(body),
1834            body_digest: digest,
1835            idempotency_key,
1836        })
1837        .await
1838    }
1839
1840    async fn list_parts(
1841        &self,
1842        bucket: wire::BucketRef,
1843        object_key: String,
1844        upload_id: String,
1845    ) -> Result<Vec<wire::UploadedPart>, ObjectsError> {
1846        let state = self.state.lock().await;
1847        let upload = state
1848            .multiparts
1849            .get(&upload_id)
1850            .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1851            .ok_or(ObjectsError::NotFound)?;
1852        Ok(upload
1853            .parts
1854            .values()
1855            .map(|(part, _)| part.clone())
1856            .collect())
1857    }
1858
1859    async fn complete_multipart(
1860        &self,
1861        bucket: wire::BucketRef,
1862        object_key: String,
1863        upload_id: String,
1864        parts: Vec<wire::UploadedPart>,
1865        idempotency_key: Option<String>,
1866    ) -> Result<wire::ObjectVersion, ObjectsError> {
1867        if parts.is_empty() || parts.len() > limits::MULTIPART_PARTS as usize {
1868            return Err(ObjectsError::Invalid("invalid multipart completion"));
1869        }
1870        let bucket_fields = Self::reference_fields(&bucket);
1871        let mut part_bytes = Vec::new();
1872        for part in &parts {
1873            part_bytes.extend_from_slice(&part.part_number.to_le_bytes());
1874            part_bytes.extend_from_slice(&(part.etag.len() as u64).to_le_bytes());
1875            part_bytes.extend_from_slice(part.etag.as_bytes());
1876            part_bytes.extend_from_slice(&part.size.to_le_bytes());
1877        }
1878        let fingerprint = Self::fingerprint(
1879            "complete-multipart",
1880            &[
1881                bucket_fields[0],
1882                bucket_fields[1],
1883                object_key.as_bytes(),
1884                upload_id.as_bytes(),
1885                &part_bytes,
1886            ],
1887        );
1888        let mut state = self.state.lock().await;
1889        Self::execute(
1890            &mut state,
1891            idempotency_key.as_deref(),
1892            fingerprint,
1893            |outcome| match outcome {
1894                MutationOutcome::Version(value) => Some(value.clone()),
1895                _ => None,
1896            },
1897            MutationOutcome::Version,
1898            |state| {
1899                let upload = state
1900                    .multiparts
1901                    .remove(&upload_id)
1902                    .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1903                    .ok_or(ObjectsError::NotFound)?;
1904                let exact = upload.parts.values().map(|(part, _)| part).eq(parts.iter());
1905                let sizes_valid = parts.iter().enumerate().all(|(index, part)| {
1906                    index + 1 == parts.len() || part.size >= limits::MIN_MULTIPART_PART_BYTES
1907                });
1908                if !exact || !sizes_valid {
1909                    state.multiparts.insert(upload_id, upload);
1910                    return Err(ObjectsError::Invalid("multipart receipts do not match"));
1911                }
1912                let current = Self::bucket(state, &bucket)?
1913                    .objects
1914                    .get(&object_key)
1915                    .and_then(|history| history.last())
1916                    .cloned();
1917                if !Self::condition(current.as_ref(), &upload.condition) {
1918                    state.multiparts.insert(upload_id, upload);
1919                    return Err(ObjectsError::PreconditionFailed);
1920                }
1921                let body_length =
1922                    match upload.parts.values().try_fold(0usize, |total, (_, body)| {
1923                        total.checked_add(body.len()).ok_or(ObjectsError::Capacity)
1924                    }) {
1925                        Ok(body_length) => body_length,
1926                        Err(error) => {
1927                            state.multiparts.insert(upload_id, upload);
1928                            return Err(error);
1929                        }
1930                    };
1931                if body_length > state.maximum_object_bytes {
1932                    state.multiparts.insert(upload_id, upload);
1933                    return Err(ObjectsError::Capacity);
1934                }
1935                let bodies = upload
1936                    .parts
1937                    .into_values()
1938                    .map(|(_, body)| body)
1939                    .collect::<Vec<_>>();
1940                let mut hasher = blake3::Hasher::new();
1941                for body in &bodies {
1942                    body.update_hash(&mut hasher)?;
1943                }
1944                let body_digest = *hasher.finalize().as_bytes();
1945                let descriptor =
1946                    Self::descriptor(state, &body_digest, body_length, upload.metadata, false)?;
1947                Self::bucket_mut(state, &bucket)?
1948                    .objects
1949                    .entry(object_key)
1950                    .or_default()
1951                    .push(Version {
1952                        descriptor: descriptor.clone(),
1953                        body: Some(StoredBody::Composite {
1954                            parts: bodies.into(),
1955                            length: body_length,
1956                        }),
1957                    });
1958                Ok(descriptor)
1959            },
1960        )
1961    }
1962
1963    async fn abort_multipart(
1964        &self,
1965        bucket: wire::BucketRef,
1966        object_key: String,
1967        upload_id: String,
1968        idempotency_key: Option<String>,
1969    ) -> Result<bool, ObjectsError> {
1970        let bucket_fields = Self::reference_fields(&bucket);
1971        let fingerprint = Self::fingerprint(
1972            "abort-multipart",
1973            &[
1974                bucket_fields[0],
1975                bucket_fields[1],
1976                object_key.as_bytes(),
1977                upload_id.as_bytes(),
1978            ],
1979        );
1980        let mut state = self.state.lock().await;
1981        Self::execute(
1982            &mut state,
1983            idempotency_key.as_deref(),
1984            fingerprint,
1985            |outcome| match outcome {
1986                MutationOutcome::Boolean(value) => Some(value.clone()),
1987                _ => None,
1988            },
1989            MutationOutcome::Boolean,
1990            |state| {
1991                let matches = state.multiparts.get(&upload_id).is_some_and(|upload| {
1992                    upload.bucket == bucket && upload.object_key == object_key
1993                });
1994                if !matches {
1995                    return Ok(false);
1996                }
1997                if let Some(upload) = state.multiparts.remove(&upload_id) {
1998                    for (_, body) in upload.parts.values() {
1999                        state.release_body(body)?;
2000                    }
2001                }
2002                Ok(true)
2003            },
2004        )
2005    }
2006}
2007
2008#[cfg(test)]
2009mod tests {
2010    use super::*;
2011
2012    fn metadata() -> wire::ObjectMetadata {
2013        wire::ObjectMetadata {
2014            content_type: "text/plain".into(),
2015            ..Default::default()
2016        }
2017    }
2018
2019    async fn put(
2020        store: &MemoryObjects,
2021        bucket: &wire::BucketRef,
2022        object_key: &str,
2023        body: &[u8],
2024        condition: Option<Condition>,
2025    ) -> wire::ObjectVersion {
2026        store
2027            .put(PutRequest {
2028                bucket: bucket.clone(),
2029                object_key: object_key.into(),
2030                body: bytes::Bytes::copy_from_slice(body),
2031                metadata: metadata(),
2032                condition,
2033                idempotency_key: None,
2034            })
2035            .await
2036            .unwrap_or_else(|_| unreachable!())
2037    }
2038
2039    #[tokio::test]
2040    async fn synchronous_bucket_composition_is_exact_and_capacity_bounded() {
2041        assert!(matches!(
2042            MemoryObjects::new(0),
2043            Err(ObjectsError::Invalid("memory byte limit must be positive"))
2044        ));
2045        assert!(MemoryObjects::with_bucket("INVALID", 4).is_err());
2046        assert!(MemoryObjects::with_bucket_limits("embedded", 5, 4).is_err());
2047
2048        let (independent, independent_bucket) =
2049            MemoryObjects::with_bucket_limits("independent", 3, 6)
2050                .unwrap_or_else(|_| unreachable!());
2051        put(&independent, &independent_bucket, "first", b"one", None).await;
2052        put(&independent, &independent_bucket, "second", b"two", None).await;
2053        assert_eq!(
2054            independent
2055                .put(PutRequest {
2056                    bucket: independent_bucket.clone(),
2057                    object_key: "aggregate-overflow".into(),
2058                    body: bytes::Bytes::from_static(b"x"),
2059                    metadata: metadata(),
2060                    condition: None,
2061                    idempotency_key: None,
2062                })
2063                .await,
2064            Err(ObjectsError::Capacity)
2065        );
2066        let (per_object, per_object_bucket) = MemoryObjects::with_bucket_limits("per-object", 3, 6)
2067            .unwrap_or_else(|_| unreachable!());
2068        assert_eq!(
2069            per_object
2070                .put(PutRequest {
2071                    bucket: per_object_bucket,
2072                    object_key: "too-large".into(),
2073                    body: bytes::Bytes::from_static(b"four"),
2074                    metadata: metadata(),
2075                    condition: None,
2076                    idempotency_key: None,
2077                })
2078                .await,
2079            Err(ObjectsError::Capacity)
2080        );
2081
2082        let (store, bucket) =
2083            MemoryObjects::with_bucket("embedded", 4).unwrap_or_else(|_| unreachable!());
2084        assert_eq!(
2085            store
2086                .head_bucket(&bucket)
2087                .await
2088                .unwrap_or_else(|_| unreachable!())
2089                .bucket,
2090            Some(bucket.clone())
2091        );
2092        put(&store, &bucket, "full", b"four", None).await;
2093        assert_eq!(
2094            store
2095                .put(PutRequest {
2096                    bucket,
2097                    object_key: "overflow".into(),
2098                    body: bytes::Bytes::from_static(b"x"),
2099                    metadata: metadata(),
2100                    condition: None,
2101                    idempotency_key: None,
2102                })
2103                .await,
2104            Err(ObjectsError::Capacity)
2105        );
2106    }
2107
2108    #[tokio::test]
2109    async fn memory_provider_passes_the_public_conformance_suite() -> Result<(), ObjectsError> {
2110        crate::conformance::verify(&MemoryObjects::default(), "conformance").await
2111    }
2112
2113    #[tokio::test]
2114    async fn shared_snapshot_and_fork_bodies_retain_exact_capacity() {
2115        let store = MemoryObjects::new(3).unwrap_or_else(|_| unreachable!());
2116        let source = store
2117            .create_bucket("source-bucket".into(), Some("create-source".into()))
2118            .await
2119            .unwrap_or_else(|_| unreachable!())
2120            .bucket
2121            .unwrap_or_else(|| unreachable!());
2122        let version = put(&store, &source, "shared", b"abc", None).await;
2123        let version_id = version.version_id.clone();
2124        let snapshot = store
2125            .snapshot(source.clone(), Some("snapshot".into()))
2126            .await
2127            .unwrap_or_else(|_| unreachable!())
2128            .snapshot
2129            .unwrap_or_else(|| unreachable!());
2130        assert!(
2131            store
2132                .delete(
2133                    source.clone(),
2134                    "shared".into(),
2135                    Some(version.version_id),
2136                    None,
2137                    Some("delete-source".into()),
2138                )
2139                .await
2140                .is_ok()
2141        );
2142        assert!(matches!(
2143            store
2144                .put(PutRequest {
2145                    bucket: source.clone(),
2146                    object_key: "replacement".into(),
2147                    body: bytes::Bytes::from_static(b"def"),
2148                    metadata: metadata(),
2149                    condition: None,
2150                    idempotency_key: None,
2151                })
2152                .await,
2153            Err(ObjectsError::Capacity)
2154        ));
2155        let fork = store
2156            .fork(
2157                ReadTarget::Snapshot(snapshot.clone()),
2158                "fork-bucket".into(),
2159                Some("fork".into()),
2160            )
2161            .await
2162            .unwrap_or_else(|_| unreachable!())
2163            .bucket
2164            .unwrap_or_else(|| unreachable!());
2165        assert!(
2166            store
2167                .destroy_snapshot(snapshot, Some("destroy-snapshot".into()))
2168                .await
2169                .unwrap_or(false)
2170        );
2171        assert!(matches!(
2172            store
2173                .put(PutRequest {
2174                    bucket: source.clone(),
2175                    object_key: "replacement".into(),
2176                    body: bytes::Bytes::from_static(b"def"),
2177                    metadata: metadata(),
2178                    condition: None,
2179                    idempotency_key: None,
2180                })
2181                .await,
2182            Err(ObjectsError::Capacity)
2183        ));
2184        assert!(
2185            store
2186                .delete(
2187                    fork,
2188                    "shared".into(),
2189                    Some(version_id),
2190                    None,
2191                    Some("delete-fork".into()),
2192                )
2193                .await
2194                .is_ok()
2195        );
2196        assert!(
2197            store
2198                .put(PutRequest {
2199                    bucket: source,
2200                    object_key: "replacement".into(),
2201                    body: bytes::Bytes::from_static(b"def"),
2202                    metadata: metadata(),
2203                    condition: None,
2204                    idempotency_key: None,
2205                })
2206                .await
2207                .is_ok()
2208        );
2209    }
2210}