use std::sync::Arc;
use eyre::Result;
use serde::{Deserialize, Serialize};
use super::{
backend::{LocalBackend, UserStorageBackend},
resolver::PersistedImmutableDataResolver,
};
const fn default_max_object_bytes() -> usize {
16 * 1024 * 1024
}
const fn default_max_objects_per_owner() -> usize {
256
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum StorageBackendConfig {
Local {
dir: std::path::PathBuf,
},
S3 {
bucket: String,
#[serde(default = "default_s3_region")]
region: String,
},
}
fn default_s3_region() -> String {
"us-west-2".to_string()
}
impl Default for StorageBackendConfig {
fn default() -> Self {
Self::Local {
dir: std::path::PathBuf::from("./data/persisted-immutable-data"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct StorageConfig {
pub backend: StorageBackendConfig,
#[serde(default = "default_max_object_bytes")]
pub max_object_bytes: usize,
#[serde(default = "default_max_objects_per_owner")]
pub max_objects_per_owner: usize,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
backend: StorageBackendConfig::default(),
max_object_bytes: default_max_object_bytes(),
max_objects_per_owner: default_max_objects_per_owner(),
}
}
}
impl StorageConfig {
pub async fn build_backend(&self) -> Result<Arc<dyn UserStorageBackend>> {
match &self.backend {
StorageBackendConfig::Local { dir } => Ok(Arc::new(LocalBackend::new(dir))),
StorageBackendConfig::S3 { bucket, region } => {
#[cfg(feature = "persisted-immutable-data-s3")]
{
let backend = super::backend::S3Backend::new(bucket.clone(), region.clone()).await?;
Ok(Arc::new(backend))
}
#[cfg(not(feature = "persisted-immutable-data-s3"))]
{
let _ = (bucket, region);
eyre::bail!("S3 storage backend requires the `persisted-immutable-data-s3` feature")
}
}
}
}
pub fn build_resolver(&self, backend: Arc<dyn UserStorageBackend>) -> PersistedImmutableDataResolver {
PersistedImmutableDataResolver::new(backend)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_local() {
let cfg = StorageConfig::default();
assert!(matches!(cfg.backend, StorageBackendConfig::Local { .. }));
assert_eq!(cfg.max_object_bytes, 16 * 1024 * 1024);
assert_eq!(cfg.max_objects_per_owner, 256);
}
#[test]
fn local_config_roundtrips() {
let json = serde_json::json!({
"max_object_bytes": 1024,
"max_objects_per_owner": 8,
"backend": { "kind": "local", "dir": "/var/newton/persisted-immutable-data" }
});
let cfg: StorageConfig = serde_json::from_value(json).unwrap();
assert_eq!(cfg.max_object_bytes, 1024);
assert_eq!(cfg.max_objects_per_owner, 8);
assert_eq!(
cfg.backend,
StorageBackendConfig::Local {
dir: "/var/newton/persisted-immutable-data".into()
}
);
}
#[test]
fn s3_config_parses_with_default_region() {
let json = serde_json::json!({
"backend": { "kind": "s3", "bucket": "newton-persisted-immutable-data-prod" }
});
let cfg: StorageConfig = serde_json::from_value(json).unwrap();
assert_eq!(
cfg.backend,
StorageBackendConfig::S3 {
bucket: "newton-persisted-immutable-data-prod".into(),
region: "us-west-2".into(),
}
);
}
#[tokio::test]
async fn build_local_backend_works() {
let tmp = tempfile::tempdir().unwrap();
let cfg = StorageConfig {
backend: StorageBackendConfig::Local {
dir: tmp.path().to_path_buf(),
},
..Default::default()
};
let backend = cfg.build_backend().await.unwrap();
backend.put("k", b"v").await.unwrap();
assert_eq!(backend.get("k").await.unwrap(), b"v");
}
}