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(remainder) = object_key.get(prefix.len()..)
1082                && let Some(position) = remainder.find(delimiter)
1083                && let Some(group) = object_key.get(..prefix.len() + position + delimiter.len())
1084            {
1085                prefixes.insert(group.to_owned());
1086                continue;
1087            }
1088            if versions {
1089                items.extend(history.iter().rev().map(|version| {
1090                    ListingItem::Entry(Box::new(wire::ListEntry {
1091                        object_key: object_key.clone(),
1092                        version: Some(version.descriptor.clone()),
1093                    }))
1094                }));
1095            } else if let Some(version) = history.last()
1096                && !version.descriptor.delete_marker
1097            {
1098                items.push(ListingItem::Entry(Box::new(wire::ListEntry {
1099                    object_key: object_key.clone(),
1100                    version: Some(version.descriptor.clone()),
1101                })));
1102            }
1103        }
1104        items.extend(prefixes.into_iter().map(ListingItem::Prefix));
1105        items.sort_by(|left, right| {
1106            let left = match left {
1107                ListingItem::Entry(value) => &value.object_key,
1108                ListingItem::Prefix(value) => value,
1109            };
1110            let right = match right {
1111                ListingItem::Entry(value) => &value.object_key,
1112                ListingItem::Prefix(value) => value,
1113            };
1114            left.cmp(right)
1115        });
1116        items
1117    }
1118
1119    async fn put_stored(
1120        &self,
1121        request: PutRequest,
1122        body: StoredBody,
1123        body_digest: [u8; 32],
1124    ) -> Result<wire::ObjectVersion, ObjectsError> {
1125        let PutRequest {
1126            bucket,
1127            object_key,
1128            body: _,
1129            metadata,
1130            condition,
1131            idempotency_key,
1132        } = request;
1133        Self::validate_key(&object_key)?;
1134        Self::validate_metadata(&metadata)?;
1135        let bucket_fields = Self::reference_fields(&bucket);
1136        let condition_bytes = Self::condition_bytes(&condition);
1137        let metadata_bytes = Self::metadata_bytes(&metadata);
1138        let fingerprint = Self::fingerprint(
1139            "put",
1140            &[
1141                bucket_fields[0],
1142                bucket_fields[1],
1143                object_key.as_bytes(),
1144                &condition_bytes,
1145                &metadata_bytes,
1146                &body_digest,
1147            ],
1148        );
1149        let mut state = self.state.lock().await;
1150        Self::execute(
1151            &mut state,
1152            idempotency_key.as_deref(),
1153            fingerprint,
1154            |outcome| match outcome {
1155                MutationOutcome::Version(value) => Some(value.clone()),
1156                _ => None,
1157            },
1158            MutationOutcome::Version,
1159            |state| {
1160                let current = Self::bucket(state, &bucket)?
1161                    .objects
1162                    .get(&object_key)
1163                    .and_then(|history| history.last())
1164                    .cloned();
1165                if !Self::condition(current.as_ref(), &condition) {
1166                    return Err(ObjectsError::PreconditionFailed);
1167                }
1168                let size = body.len();
1169                let additional = state.additional_body_bytes(&body)?;
1170                if size > state.maximum_object_bytes
1171                    || state
1172                        .bytes
1173                        .checked_add(additional)
1174                        .is_none_or(|bytes| bytes > state.maximum_bytes)
1175                {
1176                    return Err(ObjectsError::Capacity);
1177                }
1178                let descriptor = Self::descriptor(state, &body_digest, size, metadata, false)?;
1179                state.retain_body(&body)?;
1180                Self::bucket_mut(state, &bucket)?
1181                    .objects
1182                    .entry(object_key)
1183                    .or_default()
1184                    .push(Version {
1185                        descriptor: descriptor.clone(),
1186                        body: Some(body),
1187                    });
1188                Ok(descriptor)
1189            },
1190        )
1191    }
1192
1193    #[cfg(feature = "local")]
1194    pub(crate) async fn put_external(
1195        &self,
1196        request: PutRequest,
1197        external: ExternalBody,
1198    ) -> Result<wire::ObjectVersion, ObjectsError> {
1199        let ExternalBody {
1200            root,
1201            digest,
1202            length,
1203        } = external;
1204        self.put_stored(
1205            request,
1206            StoredBody::Local {
1207                root: Arc::new(root),
1208                digest,
1209                length,
1210            },
1211            digest,
1212        )
1213        .await
1214    }
1215
1216    async fn upload_part_stored(
1217        &self,
1218        request: StoredPartRequest,
1219    ) -> Result<wire::UploadedPart, ObjectsError> {
1220        let StoredPartRequest {
1221            bucket,
1222            object_key,
1223            upload_id,
1224            part_number,
1225            body,
1226            body_digest,
1227            idempotency_key,
1228        } = request;
1229        if !(1..=limits::MULTIPART_PARTS).contains(&part_number)
1230            || body.len() as u64 > limits::MAX_MULTIPART_PART_BYTES
1231        {
1232            return Err(ObjectsError::Invalid("invalid multipart part"));
1233        }
1234        let bucket_fields = Self::reference_fields(&bucket);
1235        let part_number_bytes = part_number.to_le_bytes();
1236        let fingerprint = Self::fingerprint(
1237            "upload-part",
1238            &[
1239                bucket_fields[0],
1240                bucket_fields[1],
1241                object_key.as_bytes(),
1242                upload_id.as_bytes(),
1243                &part_number_bytes,
1244                &body_digest,
1245            ],
1246        );
1247        let mut state = self.state.lock().await;
1248        Self::execute(
1249            &mut state,
1250            idempotency_key.as_deref(),
1251            fingerprint,
1252            |outcome| match outcome {
1253                MutationOutcome::Part(value) => Some(value.clone()),
1254                _ => None,
1255            },
1256            MutationOutcome::Part,
1257            |state| {
1258                let existing_body = state
1259                    .multiparts
1260                    .get(&upload_id)
1261                    .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1262                    .ok_or(ObjectsError::NotFound)?
1263                    .parts
1264                    .get(&part_number)
1265                    .map(|(_, body)| body.clone());
1266                if let Some(existing) = &existing_body {
1267                    state.release_body(existing)?;
1268                }
1269                let additional = state.additional_body_bytes(&body)?;
1270                if state
1271                    .bytes
1272                    .checked_add(additional)
1273                    .is_none_or(|bytes| bytes > state.maximum_bytes)
1274                {
1275                    if let Some(existing) = &existing_body {
1276                        state.retain_body(existing)?;
1277                    }
1278                    return Err(ObjectsError::Capacity);
1279                }
1280                let part = wire::UploadedPart {
1281                    part_number,
1282                    etag: format!("\"{}\"", blake3::Hash::from_bytes(body_digest).to_hex()),
1283                    size: body.len() as u64,
1284                };
1285                state
1286                    .multiparts
1287                    .get_mut(&upload_id)
1288                    .ok_or(ObjectsError::NotFound)?
1289                    .parts
1290                    .insert(part_number, (part.clone(), body));
1291                let admitted = state
1292                    .multiparts
1293                    .get(&upload_id)
1294                    .and_then(|upload| upload.parts.get(&part_number))
1295                    .map(|(_, body)| body.clone())
1296                    .ok_or(ObjectsError::Unavailable)?;
1297                state.retain_body(&admitted)?;
1298                Ok(part)
1299            },
1300        )
1301    }
1302
1303    #[cfg(feature = "local")]
1304    pub(crate) async fn upload_part_external(
1305        &self,
1306        bucket: wire::BucketRef,
1307        object_key: String,
1308        upload_id: String,
1309        part_number: u32,
1310        external: ExternalBody,
1311        idempotency_key: Option<String>,
1312    ) -> Result<wire::UploadedPart, ObjectsError> {
1313        let ExternalBody {
1314            root,
1315            digest,
1316            length,
1317        } = external;
1318        self.upload_part_stored(StoredPartRequest {
1319            bucket,
1320            object_key,
1321            upload_id,
1322            part_number,
1323            body: StoredBody::Local {
1324                root: Arc::new(root),
1325                digest,
1326                length,
1327            },
1328            body_digest: digest,
1329            idempotency_key,
1330        })
1331        .await
1332    }
1333}
1334
1335#[async_trait]
1336impl ObjectsProvider for MemoryObjects {
1337    async fn create_bucket(
1338        &self,
1339        name: String,
1340        idempotency_key: Option<String>,
1341    ) -> Result<wire::Bucket, ObjectsError> {
1342        Self::validate_bucket_name(&name)?;
1343        let fingerprint = Self::fingerprint("create-bucket", &[name.as_bytes()]);
1344        let mut state = self.state.lock().await;
1345        Self::execute(
1346            &mut state,
1347            idempotency_key.as_deref(),
1348            fingerprint,
1349            |outcome| match outcome {
1350                MutationOutcome::Bucket(value) => Some(value.clone()),
1351                _ => None,
1352            },
1353            MutationOutcome::Bucket,
1354            |state| {
1355                if state.names.contains_key(&name) {
1356                    return Err(ObjectsError::AlreadyExists);
1357                }
1358                let bucket_id = Self::next(state, "bucket")?;
1359                let reference = wire::BucketRef {
1360                    bucket_id: bucket_id.clone(),
1361                    name: name.clone(),
1362                };
1363                let bucket = BucketState {
1364                    reference: reference.clone(),
1365                    created_at: Self::timestamp(state),
1366                    objects: BTreeMap::new(),
1367                };
1368                state.names.insert(name, bucket_id.clone());
1369                state.buckets.insert(bucket_id, bucket.clone());
1370                Ok(wire::Bucket {
1371                    bucket: Some(reference),
1372                    created_at: Some(bucket.created_at),
1373                })
1374            },
1375        )
1376    }
1377
1378    async fn head_bucket(&self, bucket: &wire::BucketRef) -> Result<wire::Bucket, ObjectsError> {
1379        let state = self.state.lock().await;
1380        let bucket = Self::bucket(&state, bucket)?;
1381        Ok(wire::Bucket {
1382            bucket: Some(bucket.reference.clone()),
1383            created_at: Some(bucket.created_at),
1384        })
1385    }
1386
1387    async fn delete_bucket(
1388        &self,
1389        bucket: &wire::BucketRef,
1390        idempotency_key: Option<String>,
1391    ) -> Result<bool, ObjectsError> {
1392        let fields = Self::reference_fields(bucket);
1393        let fingerprint = Self::fingerprint("delete-bucket", &fields);
1394        let mut state = self.state.lock().await;
1395        Self::execute(
1396            &mut state,
1397            idempotency_key.as_deref(),
1398            fingerprint,
1399            |outcome| match outcome {
1400                MutationOutcome::Boolean(value) => Some(value.clone()),
1401                _ => None,
1402            },
1403            MutationOutcome::Boolean,
1404            |state| {
1405                let existing = Self::bucket(state, bucket)?.clone();
1406                if existing.objects.values().any(|history| !history.is_empty())
1407                    || state
1408                        .multiparts
1409                        .values()
1410                        .any(|upload| upload.bucket == *bucket)
1411                {
1412                    return Err(ObjectsError::PreconditionFailed);
1413                }
1414                state.buckets.remove(&bucket.bucket_id);
1415                state.names.remove(&bucket.name);
1416                Ok(true)
1417            },
1418        )
1419    }
1420
1421    async fn put(&self, request: PutRequest) -> Result<wire::ObjectVersion, ObjectsError> {
1422        let digest = *blake3::hash(&request.body).as_bytes();
1423        let body = StoredBody::memory(request.body.clone());
1424        self.put_stored(request, body, digest).await
1425    }
1426
1427    async fn get(&self, request: GetRequest) -> Result<BufferedObject, ObjectsError> {
1428        Self::validate_key(&request.object_key)?;
1429        let state = self.state.lock().await;
1430        let bucket = Self::target_ref(&state, &request.target)?;
1431        let version = Self::visible(bucket, &request.object_key, request.version_id.as_deref())?;
1432        if request
1433            .if_match
1434            .as_ref()
1435            .is_some_and(|etag| *etag != version.descriptor.etag)
1436            || request
1437                .if_none_match
1438                .as_ref()
1439                .is_some_and(|etag| *etag == version.descriptor.etag)
1440        {
1441            return Err(ObjectsError::PreconditionFailed);
1442        }
1443        let descriptor = version.descriptor.clone();
1444        let body = version.body.clone().ok_or(ObjectsError::NotFound)?;
1445        drop(state);
1446        let (start, end) = match request.range {
1447            None => (0, body.len()),
1448            Some((start, end)) => {
1449                let start =
1450                    usize::try_from(start).map_err(|_| ObjectsError::Invalid("invalid range"))?;
1451                let end = usize::try_from(end.unwrap_or(body.len().saturating_sub(1) as u64))
1452                    .map_err(|_| ObjectsError::Invalid("invalid range"))?;
1453                if start > end || end >= body.len() {
1454                    return Err(ObjectsError::Invalid("invalid range"));
1455                }
1456                (start, end.saturating_add(1))
1457            }
1458        };
1459        if end.saturating_sub(start) as u64 > request.maximum_bytes {
1460            return Err(ObjectsError::Capacity);
1461        }
1462        let selected = body.read_async(start, end).await?;
1463        Ok(BufferedObject {
1464            version: descriptor,
1465            body: selected,
1466        })
1467    }
1468
1469    async fn delete(
1470        &self,
1471        bucket: wire::BucketRef,
1472        object_key: String,
1473        version_id: Option<String>,
1474        condition: Option<Condition>,
1475        idempotency_key: Option<String>,
1476    ) -> Result<DeleteResult, ObjectsError> {
1477        Self::validate_key(&object_key)?;
1478        let bucket_fields = Self::reference_fields(&bucket);
1479        let condition_bytes = Self::condition_bytes(&condition);
1480        let fingerprint = Self::fingerprint(
1481            "delete",
1482            &[
1483                bucket_fields[0],
1484                bucket_fields[1],
1485                object_key.as_bytes(),
1486                version_id.as_deref().unwrap_or_default().as_bytes(),
1487                &condition_bytes,
1488            ],
1489        );
1490        let mut state = self.state.lock().await;
1491        Self::execute(
1492            &mut state,
1493            idempotency_key.as_deref(),
1494            fingerprint,
1495            |outcome| match outcome {
1496                MutationOutcome::Delete(value) => Some(value.clone()),
1497                _ => None,
1498            },
1499            MutationOutcome::Delete,
1500            |state| {
1501                let current = Self::bucket(state, &bucket)?
1502                    .objects
1503                    .get(&object_key)
1504                    .and_then(|history| history.last())
1505                    .cloned();
1506                if !Self::condition(current.as_ref(), &condition) {
1507                    return Err(ObjectsError::PreconditionFailed);
1508                }
1509                if let Some(identity) = version_id {
1510                    let (removed_body, empty) = {
1511                        let Some(history) = Self::bucket_mut(state, &bucket)?
1512                            .objects
1513                            .get_mut(&object_key)
1514                        else {
1515                            return Ok(DeleteResult {
1516                                existed: false,
1517                                marker: None,
1518                            });
1519                        };
1520                        let Some(position) = history
1521                            .iter()
1522                            .position(|version| version.descriptor.version_id == identity)
1523                        else {
1524                            return Ok(DeleteResult {
1525                                existed: false,
1526                                marker: None,
1527                            });
1528                        };
1529                        let removed = history.remove(position);
1530                        (removed.body, history.is_empty())
1531                    };
1532                    if let Some(body) = removed_body {
1533                        state.release_body(&body)?;
1534                    }
1535                    if empty {
1536                        Self::bucket_mut(state, &bucket)?
1537                            .objects
1538                            .remove(&object_key);
1539                    }
1540                    Ok(DeleteResult {
1541                        existed: true,
1542                        marker: None,
1543                    })
1544                } else {
1545                    let marker = Self::descriptor(
1546                        state,
1547                        blake3::hash(&[]).as_bytes(),
1548                        0,
1549                        wire::ObjectMetadata::default(),
1550                        true,
1551                    )?;
1552                    Self::bucket_mut(state, &bucket)?
1553                        .objects
1554                        .entry(object_key)
1555                        .or_default()
1556                        .push(Version {
1557                            descriptor: marker.clone(),
1558                            body: None,
1559                        });
1560                    Ok(DeleteResult {
1561                        existed: true,
1562                        marker: Some(marker),
1563                    })
1564                }
1565            },
1566        )
1567    }
1568
1569    async fn list(
1570        &self,
1571        target: ReadTarget,
1572        prefix: String,
1573        delimiter: Option<String>,
1574        versions: bool,
1575        page_size: u32,
1576        continuation: Option<String>,
1577    ) -> Result<ProviderListPage, ObjectsError> {
1578        if page_size == 0
1579            || page_size > limits::LIST_PAGE_ENTRIES
1580            || delimiter.as_ref().is_some_and(String::is_empty)
1581        {
1582            return Err(ObjectsError::Invalid("invalid listing"));
1583        }
1584        let mut state = self.state.lock().await;
1585        let binding = Self::binding(&target, &prefix, delimiter.as_deref(), versions);
1586        let (view_id, offset) = if let Some(token) = continuation {
1587            let (view, offset) = token
1588                .rsplit_once(':')
1589                .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1590            let offset = offset
1591                .parse::<usize>()
1592                .map_err(|_| ObjectsError::Invalid("invalid continuation"))?;
1593            (view.to_owned(), offset)
1594        } else {
1595            let bucket = Self::target(&state, &target)?;
1596            let view_id = Self::next(&mut state, "listing")?;
1597            state.listings.insert(
1598                view_id.clone(),
1599                ListingView {
1600                    binding: binding.clone(),
1601                    items: Self::listing_items(&bucket, &prefix, delimiter.as_deref(), versions),
1602                },
1603            );
1604            (view_id, 0)
1605        };
1606        let view = state
1607            .listings
1608            .get(&view_id)
1609            .filter(|view| view.binding == binding)
1610            .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1611        if offset > view.items.len() {
1612            return Err(ObjectsError::Invalid("invalid continuation"));
1613        }
1614        let end = offset
1615            .saturating_add(page_size as usize)
1616            .min(view.items.len());
1617        let mut entries = Vec::new();
1618        let mut common_prefixes = Vec::new();
1619        let page = view
1620            .items
1621            .get(offset..end)
1622            .ok_or(ObjectsError::Invalid("invalid continuation"))?;
1623        for item in page {
1624            match item {
1625                ListingItem::Entry(value) => entries.push(value.as_ref().clone()),
1626                ListingItem::Prefix(value) => common_prefixes.push(value.clone()),
1627            }
1628        }
1629        Ok(ProviderListPage {
1630            entries,
1631            common_prefixes,
1632            continuation: (end < view.items.len()).then(|| format!("{view_id}:{end}")),
1633        })
1634    }
1635
1636    async fn snapshot(
1637        &self,
1638        bucket: wire::BucketRef,
1639        idempotency_key: Option<String>,
1640    ) -> Result<wire::Snapshot, ObjectsError> {
1641        let fields = Self::reference_fields(&bucket);
1642        let fingerprint = Self::fingerprint("snapshot", &fields);
1643        let mut state = self.state.lock().await;
1644        Self::execute(
1645            &mut state,
1646            idempotency_key.as_deref(),
1647            fingerprint,
1648            |outcome| match outcome {
1649                MutationOutcome::Snapshot(value) => Some(value.clone()),
1650                _ => None,
1651            },
1652            MutationOutcome::Snapshot,
1653            |state| {
1654                let source = Self::bucket(state, &bucket)?.clone();
1655                state.retain_bucket(&source)?;
1656                let snapshot_id = Self::next(state, "snapshot")?;
1657                let reference = wire::SnapshotRef {
1658                    snapshot_id: snapshot_id.clone(),
1659                    source_bucket_id: bucket.bucket_id,
1660                };
1661                let created_at = Self::timestamp(state);
1662                state.snapshots.insert(
1663                    snapshot_id,
1664                    SnapshotState {
1665                        reference: reference.clone(),
1666                        bucket: source,
1667                    },
1668                );
1669                Ok(wire::Snapshot {
1670                    snapshot: Some(reference),
1671                    created_at: Some(created_at),
1672                })
1673            },
1674        )
1675    }
1676
1677    async fn destroy_snapshot(
1678        &self,
1679        snapshot: wire::SnapshotRef,
1680        idempotency_key: Option<String>,
1681    ) -> Result<bool, ObjectsError> {
1682        let fingerprint = Self::fingerprint(
1683            "destroy-snapshot",
1684            &[
1685                snapshot.snapshot_id.as_bytes(),
1686                snapshot.source_bucket_id.as_bytes(),
1687            ],
1688        );
1689        let mut state = self.state.lock().await;
1690        Self::execute(
1691            &mut state,
1692            idempotency_key.as_deref(),
1693            fingerprint,
1694            |outcome| match outcome {
1695                MutationOutcome::Boolean(value) => Some(value.clone()),
1696                _ => None,
1697            },
1698            MutationOutcome::Boolean,
1699            |state| {
1700                let exists = state
1701                    .snapshots
1702                    .get(&snapshot.snapshot_id)
1703                    .is_some_and(|value| value.reference == snapshot);
1704                if !exists {
1705                    return Ok(false);
1706                }
1707                let removed = state
1708                    .snapshots
1709                    .remove(&snapshot.snapshot_id)
1710                    .ok_or(ObjectsError::Unavailable)?;
1711                state.release_bucket(&removed.bucket)?;
1712                Ok(true)
1713            },
1714        )
1715    }
1716
1717    async fn fork(
1718        &self,
1719        source: ReadTarget,
1720        destination_name: String,
1721        idempotency_key: Option<String>,
1722    ) -> Result<wire::Bucket, ObjectsError> {
1723        let source_identity = match &source {
1724            ReadTarget::Bucket(value) => format!("bucket:{}:{}", value.bucket_id, value.name),
1725            ReadTarget::Snapshot(value) => {
1726                format!("snapshot:{}:{}", value.snapshot_id, value.source_bucket_id)
1727            }
1728        };
1729        let fingerprint = Self::fingerprint(
1730            "fork",
1731            &[source_identity.as_bytes(), destination_name.as_bytes()],
1732        );
1733        let mut state = self.state.lock().await;
1734        Self::execute(
1735            &mut state,
1736            idempotency_key.as_deref(),
1737            fingerprint,
1738            |outcome| match outcome {
1739                MutationOutcome::Bucket(value) => Some(value.clone()),
1740                _ => None,
1741            },
1742            MutationOutcome::Bucket,
1743            |state| {
1744                if state.names.contains_key(&destination_name) {
1745                    return Err(ObjectsError::AlreadyExists);
1746                }
1747                let source = Self::target(state, &source)?;
1748                state.retain_bucket(&source)?;
1749                let bucket_id = Self::next(state, "bucket")?;
1750                let reference = wire::BucketRef {
1751                    bucket_id: bucket_id.clone(),
1752                    name: destination_name.clone(),
1753                };
1754                let created_at = Self::timestamp(state);
1755                state.names.insert(destination_name, bucket_id.clone());
1756                state.buckets.insert(
1757                    bucket_id,
1758                    BucketState {
1759                        reference: reference.clone(),
1760                        created_at,
1761                        objects: source.objects,
1762                    },
1763                );
1764                Ok(wire::Bucket {
1765                    bucket: Some(reference),
1766                    created_at: Some(created_at),
1767                })
1768            },
1769        )
1770    }
1771
1772    async fn create_multipart(
1773        &self,
1774        bucket: wire::BucketRef,
1775        object_key: String,
1776        metadata: wire::ObjectMetadata,
1777        condition: Option<Condition>,
1778        idempotency_key: Option<String>,
1779    ) -> Result<wire::MultipartUpload, ObjectsError> {
1780        Self::validate_key(&object_key)?;
1781        Self::validate_metadata(&metadata)?;
1782        let bucket_fields = Self::reference_fields(&bucket);
1783        let metadata_bytes = Self::metadata_bytes(&metadata);
1784        let condition_bytes = Self::condition_bytes(&condition);
1785        let fingerprint = Self::fingerprint(
1786            "create-multipart",
1787            &[
1788                bucket_fields[0],
1789                bucket_fields[1],
1790                object_key.as_bytes(),
1791                &metadata_bytes,
1792                &condition_bytes,
1793            ],
1794        );
1795        let mut state = self.state.lock().await;
1796        Self::execute(
1797            &mut state,
1798            idempotency_key.as_deref(),
1799            fingerprint,
1800            |outcome| match outcome {
1801                MutationOutcome::Multipart(value) => Some(value.clone()),
1802                _ => None,
1803            },
1804            MutationOutcome::Multipart,
1805            |state| {
1806                Self::bucket(state, &bucket)?;
1807                let upload_id = Self::next(state, "upload")?;
1808                state.multiparts.insert(
1809                    upload_id.clone(),
1810                    MultipartState {
1811                        bucket,
1812                        object_key,
1813                        metadata,
1814                        condition,
1815                        parts: BTreeMap::new(),
1816                    },
1817                );
1818                Ok(wire::MultipartUpload { upload_id })
1819            },
1820        )
1821    }
1822
1823    async fn upload_part(
1824        &self,
1825        bucket: wire::BucketRef,
1826        object_key: String,
1827        upload_id: String,
1828        part_number: u32,
1829        body: bytes::Bytes,
1830        idempotency_key: Option<String>,
1831    ) -> Result<wire::UploadedPart, ObjectsError> {
1832        let digest = *blake3::hash(&body).as_bytes();
1833        self.upload_part_stored(StoredPartRequest {
1834            bucket,
1835            object_key,
1836            upload_id,
1837            part_number,
1838            body: StoredBody::memory(body),
1839            body_digest: digest,
1840            idempotency_key,
1841        })
1842        .await
1843    }
1844
1845    async fn list_parts(
1846        &self,
1847        bucket: wire::BucketRef,
1848        object_key: String,
1849        upload_id: String,
1850    ) -> Result<Vec<wire::UploadedPart>, ObjectsError> {
1851        let state = self.state.lock().await;
1852        let upload = state
1853            .multiparts
1854            .get(&upload_id)
1855            .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1856            .ok_or(ObjectsError::NotFound)?;
1857        Ok(upload
1858            .parts
1859            .values()
1860            .map(|(part, _)| part.clone())
1861            .collect())
1862    }
1863
1864    async fn complete_multipart(
1865        &self,
1866        bucket: wire::BucketRef,
1867        object_key: String,
1868        upload_id: String,
1869        parts: Vec<wire::UploadedPart>,
1870        idempotency_key: Option<String>,
1871    ) -> Result<wire::ObjectVersion, ObjectsError> {
1872        if parts.is_empty() || parts.len() > limits::MULTIPART_PARTS as usize {
1873            return Err(ObjectsError::Invalid("invalid multipart completion"));
1874        }
1875        let bucket_fields = Self::reference_fields(&bucket);
1876        let mut part_bytes = Vec::new();
1877        for part in &parts {
1878            part_bytes.extend_from_slice(&part.part_number.to_le_bytes());
1879            part_bytes.extend_from_slice(&(part.etag.len() as u64).to_le_bytes());
1880            part_bytes.extend_from_slice(part.etag.as_bytes());
1881            part_bytes.extend_from_slice(&part.size.to_le_bytes());
1882        }
1883        let fingerprint = Self::fingerprint(
1884            "complete-multipart",
1885            &[
1886                bucket_fields[0],
1887                bucket_fields[1],
1888                object_key.as_bytes(),
1889                upload_id.as_bytes(),
1890                &part_bytes,
1891            ],
1892        );
1893        let mut state = self.state.lock().await;
1894        Self::execute(
1895            &mut state,
1896            idempotency_key.as_deref(),
1897            fingerprint,
1898            |outcome| match outcome {
1899                MutationOutcome::Version(value) => Some(value.clone()),
1900                _ => None,
1901            },
1902            MutationOutcome::Version,
1903            |state| {
1904                let upload = state
1905                    .multiparts
1906                    .remove(&upload_id)
1907                    .filter(|upload| upload.bucket == bucket && upload.object_key == object_key)
1908                    .ok_or(ObjectsError::NotFound)?;
1909                let exact = upload.parts.values().map(|(part, _)| part).eq(parts.iter());
1910                let sizes_valid = parts.iter().enumerate().all(|(index, part)| {
1911                    index + 1 == parts.len() || part.size >= limits::MIN_MULTIPART_PART_BYTES
1912                });
1913                if !exact || !sizes_valid {
1914                    state.multiparts.insert(upload_id, upload);
1915                    return Err(ObjectsError::Invalid("multipart receipts do not match"));
1916                }
1917                let current = Self::bucket(state, &bucket)?
1918                    .objects
1919                    .get(&object_key)
1920                    .and_then(|history| history.last())
1921                    .cloned();
1922                if !Self::condition(current.as_ref(), &upload.condition) {
1923                    state.multiparts.insert(upload_id, upload);
1924                    return Err(ObjectsError::PreconditionFailed);
1925                }
1926                let body_length =
1927                    match upload.parts.values().try_fold(0usize, |total, (_, body)| {
1928                        total.checked_add(body.len()).ok_or(ObjectsError::Capacity)
1929                    }) {
1930                        Ok(body_length) => body_length,
1931                        Err(error) => {
1932                            state.multiparts.insert(upload_id, upload);
1933                            return Err(error);
1934                        }
1935                    };
1936                if body_length > state.maximum_object_bytes {
1937                    state.multiparts.insert(upload_id, upload);
1938                    return Err(ObjectsError::Capacity);
1939                }
1940                let bodies = upload
1941                    .parts
1942                    .into_values()
1943                    .map(|(_, body)| body)
1944                    .collect::<Vec<_>>();
1945                let mut hasher = blake3::Hasher::new();
1946                for body in &bodies {
1947                    body.update_hash(&mut hasher)?;
1948                }
1949                let body_digest = *hasher.finalize().as_bytes();
1950                let descriptor =
1951                    Self::descriptor(state, &body_digest, body_length, upload.metadata, false)?;
1952                Self::bucket_mut(state, &bucket)?
1953                    .objects
1954                    .entry(object_key)
1955                    .or_default()
1956                    .push(Version {
1957                        descriptor: descriptor.clone(),
1958                        body: Some(StoredBody::Composite {
1959                            parts: bodies.into(),
1960                            length: body_length,
1961                        }),
1962                    });
1963                Ok(descriptor)
1964            },
1965        )
1966    }
1967
1968    async fn abort_multipart(
1969        &self,
1970        bucket: wire::BucketRef,
1971        object_key: String,
1972        upload_id: String,
1973        idempotency_key: Option<String>,
1974    ) -> Result<bool, ObjectsError> {
1975        let bucket_fields = Self::reference_fields(&bucket);
1976        let fingerprint = Self::fingerprint(
1977            "abort-multipart",
1978            &[
1979                bucket_fields[0],
1980                bucket_fields[1],
1981                object_key.as_bytes(),
1982                upload_id.as_bytes(),
1983            ],
1984        );
1985        let mut state = self.state.lock().await;
1986        Self::execute(
1987            &mut state,
1988            idempotency_key.as_deref(),
1989            fingerprint,
1990            |outcome| match outcome {
1991                MutationOutcome::Boolean(value) => Some(value.clone()),
1992                _ => None,
1993            },
1994            MutationOutcome::Boolean,
1995            |state| {
1996                let matches = state.multiparts.get(&upload_id).is_some_and(|upload| {
1997                    upload.bucket == bucket && upload.object_key == object_key
1998                });
1999                if !matches {
2000                    return Ok(false);
2001                }
2002                if let Some(upload) = state.multiparts.remove(&upload_id) {
2003                    for (_, body) in upload.parts.values() {
2004                        state.release_body(body)?;
2005                    }
2006                }
2007                Ok(true)
2008            },
2009        )
2010    }
2011}
2012
2013#[cfg(test)]
2014mod tests {
2015    use super::*;
2016
2017    fn metadata() -> wire::ObjectMetadata {
2018        wire::ObjectMetadata {
2019            content_type: "text/plain".into(),
2020            ..Default::default()
2021        }
2022    }
2023
2024    async fn put(
2025        store: &MemoryObjects,
2026        bucket: &wire::BucketRef,
2027        object_key: &str,
2028        body: &[u8],
2029        condition: Option<Condition>,
2030    ) -> wire::ObjectVersion {
2031        store
2032            .put(PutRequest {
2033                bucket: bucket.clone(),
2034                object_key: object_key.into(),
2035                body: bytes::Bytes::copy_from_slice(body),
2036                metadata: metadata(),
2037                condition,
2038                idempotency_key: None,
2039            })
2040            .await
2041            .unwrap_or_else(|_| unreachable!())
2042    }
2043
2044    #[tokio::test]
2045    async fn synchronous_bucket_composition_is_exact_and_capacity_bounded() {
2046        assert!(matches!(
2047            MemoryObjects::new(0),
2048            Err(ObjectsError::Invalid("memory byte limit must be positive"))
2049        ));
2050        assert!(MemoryObjects::with_bucket("INVALID", 4).is_err());
2051        assert!(MemoryObjects::with_bucket_limits("embedded", 5, 4).is_err());
2052
2053        let (independent, independent_bucket) =
2054            MemoryObjects::with_bucket_limits("independent", 3, 6)
2055                .unwrap_or_else(|_| unreachable!());
2056        put(&independent, &independent_bucket, "first", b"one", None).await;
2057        put(&independent, &independent_bucket, "second", b"two", None).await;
2058        assert_eq!(
2059            independent
2060                .put(PutRequest {
2061                    bucket: independent_bucket.clone(),
2062                    object_key: "aggregate-overflow".into(),
2063                    body: bytes::Bytes::from_static(b"x"),
2064                    metadata: metadata(),
2065                    condition: None,
2066                    idempotency_key: None,
2067                })
2068                .await,
2069            Err(ObjectsError::Capacity)
2070        );
2071        let (per_object, per_object_bucket) = MemoryObjects::with_bucket_limits("per-object", 3, 6)
2072            .unwrap_or_else(|_| unreachable!());
2073        assert_eq!(
2074            per_object
2075                .put(PutRequest {
2076                    bucket: per_object_bucket,
2077                    object_key: "too-large".into(),
2078                    body: bytes::Bytes::from_static(b"four"),
2079                    metadata: metadata(),
2080                    condition: None,
2081                    idempotency_key: None,
2082                })
2083                .await,
2084            Err(ObjectsError::Capacity)
2085        );
2086
2087        let (store, bucket) =
2088            MemoryObjects::with_bucket("embedded", 4).unwrap_or_else(|_| unreachable!());
2089        assert_eq!(
2090            store
2091                .head_bucket(&bucket)
2092                .await
2093                .unwrap_or_else(|_| unreachable!())
2094                .bucket,
2095            Some(bucket.clone())
2096        );
2097        put(&store, &bucket, "full", b"four", None).await;
2098        assert_eq!(
2099            store
2100                .put(PutRequest {
2101                    bucket,
2102                    object_key: "overflow".into(),
2103                    body: bytes::Bytes::from_static(b"x"),
2104                    metadata: metadata(),
2105                    condition: None,
2106                    idempotency_key: None,
2107                })
2108                .await,
2109            Err(ObjectsError::Capacity)
2110        );
2111    }
2112
2113    #[tokio::test]
2114    async fn memory_provider_passes_the_public_conformance_suite() -> Result<(), ObjectsError> {
2115        crate::conformance::verify(&MemoryObjects::default(), "conformance").await
2116    }
2117
2118    #[tokio::test]
2119    async fn shared_snapshot_and_fork_bodies_retain_exact_capacity() {
2120        let store = MemoryObjects::new(3).unwrap_or_else(|_| unreachable!());
2121        let source = store
2122            .create_bucket("source-bucket".into(), Some("create-source".into()))
2123            .await
2124            .unwrap_or_else(|_| unreachable!())
2125            .bucket
2126            .unwrap_or_else(|| unreachable!());
2127        let version = put(&store, &source, "shared", b"abc", None).await;
2128        let version_id = version.version_id.clone();
2129        let snapshot = store
2130            .snapshot(source.clone(), Some("snapshot".into()))
2131            .await
2132            .unwrap_or_else(|_| unreachable!())
2133            .snapshot
2134            .unwrap_or_else(|| unreachable!());
2135        assert!(
2136            store
2137                .delete(
2138                    source.clone(),
2139                    "shared".into(),
2140                    Some(version.version_id),
2141                    None,
2142                    Some("delete-source".into()),
2143                )
2144                .await
2145                .is_ok()
2146        );
2147        assert!(matches!(
2148            store
2149                .put(PutRequest {
2150                    bucket: source.clone(),
2151                    object_key: "replacement".into(),
2152                    body: bytes::Bytes::from_static(b"def"),
2153                    metadata: metadata(),
2154                    condition: None,
2155                    idempotency_key: None,
2156                })
2157                .await,
2158            Err(ObjectsError::Capacity)
2159        ));
2160        let fork = store
2161            .fork(
2162                ReadTarget::Snapshot(snapshot.clone()),
2163                "fork-bucket".into(),
2164                Some("fork".into()),
2165            )
2166            .await
2167            .unwrap_or_else(|_| unreachable!())
2168            .bucket
2169            .unwrap_or_else(|| unreachable!());
2170        assert!(
2171            store
2172                .destroy_snapshot(snapshot, Some("destroy-snapshot".into()))
2173                .await
2174                .unwrap_or(false)
2175        );
2176        assert!(matches!(
2177            store
2178                .put(PutRequest {
2179                    bucket: source.clone(),
2180                    object_key: "replacement".into(),
2181                    body: bytes::Bytes::from_static(b"def"),
2182                    metadata: metadata(),
2183                    condition: None,
2184                    idempotency_key: None,
2185                })
2186                .await,
2187            Err(ObjectsError::Capacity)
2188        ));
2189        assert!(
2190            store
2191                .delete(
2192                    fork,
2193                    "shared".into(),
2194                    Some(version_id),
2195                    None,
2196                    Some("delete-fork".into()),
2197                )
2198                .await
2199                .is_ok()
2200        );
2201        assert!(
2202            store
2203                .put(PutRequest {
2204                    bucket: source,
2205                    object_key: "replacement".into(),
2206                    body: bytes::Bytes::from_static(b"def"),
2207                    metadata: metadata(),
2208                    condition: None,
2209                    idempotency_key: None,
2210                })
2211                .await
2212                .is_ok()
2213        );
2214    }
2215}