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}