1use std::sync::Arc;
10
11use khive_db::stores::blob_s3::{S3BlobStore, S3BlobStoreConfig};
12use khive_db::{SqliteError, StorageBackend};
13use khive_storage::BlobStore;
14
15use crate::engine_config::BlobConfig;
16use crate::KhiveConfig;
17
18pub fn resolve_blob_store(
27 cfg: &KhiveConfig,
28 backend: &StorageBackend,
29) -> Result<Arc<dyn BlobStore>, SqliteError> {
30 match &cfg.storage.blob {
31 None => backend.blob_store(None, None),
32 Some(BlobConfig::Fs { root, floor_bytes }) => {
33 let root_path = root.as_ref().map(std::path::PathBuf::from);
34 backend.blob_store(root_path.as_deref(), *floor_bytes)
35 }
36 Some(BlobConfig::S3 {
37 bucket,
38 region,
39 endpoint,
40 prefix,
41 allow_http,
42 }) => {
43 let mut s3_cfg = S3BlobStoreConfig::new(bucket.clone(), region.clone());
44 if let Some(endpoint) = endpoint {
45 s3_cfg = s3_cfg.with_endpoint(endpoint.clone());
46 }
47 if let Some(prefix) = prefix {
48 s3_cfg = s3_cfg.with_prefix(prefix.clone());
49 }
50 if let Some(allow_http) = allow_http {
51 s3_cfg = s3_cfg.with_allow_http(*allow_http);
52 }
53 let store = S3BlobStore::new(s3_cfg)?;
54 Ok(Arc::new(store))
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use crate::engine_config::StorageSectionConfig;
63
64 fn memory_backend() -> StorageBackend {
65 StorageBackend::memory().expect("memory backend should create")
66 }
67
68 #[test]
69 fn absent_storage_section_selects_fs_with_explicit_root() {
70 let dir = tempfile::tempdir().unwrap();
75 let backend = memory_backend();
76 let cfg = KhiveConfig::default();
77 let err = match resolve_blob_store(&cfg, &backend) {
82 Err(e) => e,
83 Ok(_) => panic!("expected an error for an in-memory backend with no root override"),
84 };
85 assert!(matches!(err, SqliteError::InvalidData(_)));
86 drop(dir);
87 }
88
89 #[test]
90 fn explicit_fs_root_is_selected() {
91 let dir = tempfile::tempdir().unwrap();
92 let backend = memory_backend();
93 let cfg = KhiveConfig {
94 storage: StorageSectionConfig {
95 blob: Some(BlobConfig::Fs {
96 root: Some(dir.path().to_string_lossy().into_owned()),
97 floor_bytes: Some(0),
98 }),
99 },
100 ..KhiveConfig::default()
101 };
102 let store = resolve_blob_store(&cfg, &backend).expect("fs store should build");
103 drop(store);
104 }
105
106 #[test]
107 fn s3_backend_selection_reaches_s3_construction() {
108 let _guard = ENV_LOCK.lock().unwrap();
112 std::env::remove_var("AWS_ACCESS_KEY_ID");
113 std::env::remove_var("AWS_SECRET_ACCESS_KEY");
114 let backend = memory_backend();
115 let cfg = KhiveConfig {
116 storage: StorageSectionConfig {
117 blob: Some(BlobConfig::S3 {
118 bucket: "khive-blobs".to_string(),
119 region: "us-east-1".to_string(),
120 endpoint: None,
121 prefix: None,
122 allow_http: None,
123 }),
124 },
125 ..KhiveConfig::default()
126 };
127 let err = match resolve_blob_store(&cfg, &backend) {
128 Err(e) => e,
129 Ok(_) => panic!("expected the credential-env error with no AWS env vars set"),
130 };
131 let msg = err.to_string();
132 assert!(
133 msg.contains("AWS_ACCESS_KEY_ID"),
134 "expected the credential-env error, got: {msg}"
135 );
136 }
137
138 static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
142}