Skip to main content

backbone_bucket/storage/
mod.rs

1//! Object storage abstraction.
2//!
3//! The [`ObjectStorage`] trait is the single boundary between the bucket
4//! module and whichever backend is serving bytes — local filesystem,
5//! MinIO, Amazon S3, or any S3-compatible service.
6//!
7//! # Backends
8//!
9//! - [`LocalStorage`] — filesystem; emits module-signed HMAC URLs for
10//!   presigned access. Suitable for development and single-node deployments.
11//! - [`S3Storage`] — AWS S3 / MinIO via `aws-sdk-s3`; emits real SigV4
12//!   presigned URLs. Requires the `s3` feature.
13//!
14//! # Design
15//!
16//! - Trait object safe: consumers wire an `Arc<dyn ObjectStorage>` into
17//!   [`crate::BucketModule`] at build time.
18//! - All trait methods except `public_url` are async. `aws-sdk-s3`'s
19//!   presigner requires a Tokio runtime context for its internal timer
20//!   infrastructure, so `presigned_get` / `presigned_put` are async too.
21//! - `public_url` returns `None` when the configured backend has no public
22//!   bucket or the key does not map to the public prefix.
23
24use std::time::Duration;
25
26use async_trait::async_trait;
27use bytes::Bytes;
28use chrono::{DateTime, Utc};
29use url::Url;
30
31use crate::error::BucketResult;
32
33pub mod local;
34pub use local::LocalStorage;
35
36#[cfg(feature = "s3")]
37pub mod s3;
38#[cfg(feature = "s3")]
39pub use s3::S3Storage;
40
41#[cfg(feature = "test-utils")]
42pub mod memory;
43#[cfg(feature = "test-utils")]
44pub use memory::InMemoryStorage;
45
46/// Metadata returned from `head` without fetching the body.
47#[derive(Debug, Clone)]
48pub struct ObjectMeta {
49    pub key: String,
50    pub size: u64,
51    pub content_type: Option<String>,
52    pub etag: Option<String>,
53    pub last_modified: Option<DateTime<Utc>>,
54}
55
56/// Object storage backend contract.
57///
58/// Beta scope is buffered reads via `get`; streaming `Body::from_stream`
59/// variants are planned post-beta (see `docs/serving.md`).
60#[async_trait]
61pub trait ObjectStorage: Send + Sync {
62    /// Upload an object under `key` with the given content type.
63    async fn put(&self, key: &str, body: Bytes, content_type: &str) -> BucketResult<()>;
64
65    /// Download the full object body (buffered).
66    async fn get(&self, key: &str) -> BucketResult<Bytes>;
67
68    /// Remove the object. Idempotent: absent keys return `Ok(())`.
69    async fn delete(&self, key: &str) -> BucketResult<()>;
70
71    /// Stat the object without fetching the body.
72    async fn head(&self, key: &str) -> BucketResult<ObjectMeta>;
73
74    /// Short-lived signed URL for direct GET.
75    ///
76    /// Used by mode-B redirect serving and by mode-C direct clients.
77    async fn presigned_get(&self, key: &str, ttl: Duration) -> BucketResult<Url>;
78
79    /// Short-lived signed URL for direct PUT (browser direct-upload flow).
80    async fn presigned_put(
81        &self,
82        key: &str,
83        ttl: Duration,
84        content_type: &str,
85    ) -> BucketResult<Url>;
86
87    /// Public URL for keys that map to the configured public bucket / prefix,
88    /// or `None` when the backend has no public side or the key doesn't qualify.
89    ///
90    /// Consumed by mode-A fast-path callers that want to bypass the service hop.
91    fn public_url(&self, key: &str) -> Option<Url>;
92}