1#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum StoreConfig {
12 Fs { root: String },
14 S3 {
18 bucket: String,
19 endpoint: Option<String>,
20 region: Option<String>,
21 },
22}
23
24impl StoreConfig {
25 pub fn from_env(get: impl Fn(&str) -> Option<String>) -> Result<StoreConfig, String> {
37 match get("GONZALO_STORE").as_deref() {
38 None | Some("") | Some("fs") => Ok(StoreConfig::Fs {
39 root: get("GONZALO_ROOT").unwrap_or_else(|| "./gonzalo-data".into()),
40 }),
41 Some("s3") => {
42 let bucket = get("GONZALO_S3_BUCKET")
43 .filter(|b| !b.is_empty())
44 .ok_or("GONZALO_STORE=s3 requires GONZALO_S3_BUCKET")?;
45 Ok(StoreConfig::S3 {
46 bucket,
47 endpoint: get("GONZALO_S3_ENDPOINT").filter(|s| !s.is_empty()),
48 region: get("GONZALO_S3_REGION").filter(|s| !s.is_empty()),
49 })
50 }
51 Some(other) => Err(format!(
52 "unknown GONZALO_STORE={other:?} (expected \"fs\" or \"s3\")"
53 )),
54 }
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use std::collections::HashMap;
62
63 fn env(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
65 let map: HashMap<String, String> = pairs
66 .iter()
67 .map(|(k, v)| (k.to_string(), v.to_string()))
68 .collect();
69 move |k: &str| map.get(k).cloned()
70 }
71
72 #[test]
73 fn default_is_fs_with_default_root() {
74 let cfg = StoreConfig::from_env(env(&[])).unwrap();
75 assert_eq!(
76 cfg,
77 StoreConfig::Fs {
78 root: "./gonzalo-data".into()
79 }
80 );
81 }
82
83 #[test]
84 fn fs_honors_root() {
85 let cfg = StoreConfig::from_env(env(&[("GONZALO_STORE", "fs"), ("GONZALO_ROOT", "/data")]))
86 .unwrap();
87 assert_eq!(
88 cfg,
89 StoreConfig::Fs {
90 root: "/data".into()
91 }
92 );
93 }
94
95 #[test]
96 fn s3_requires_bucket() {
97 let err = StoreConfig::from_env(env(&[("GONZALO_STORE", "s3")])).unwrap_err();
98 assert!(err.contains("GONZALO_S3_BUCKET"), "got {err}");
99 }
100
101 #[test]
102 fn s3_reads_bucket_endpoint_region() {
103 let cfg = StoreConfig::from_env(env(&[
104 ("GONZALO_STORE", "s3"),
105 ("GONZALO_S3_BUCKET", "gonzalo"),
106 ("GONZALO_S3_ENDPOINT", "http://garage:3900"),
107 ("GONZALO_S3_REGION", "garage"),
108 ]))
109 .unwrap();
110 assert_eq!(
111 cfg,
112 StoreConfig::S3 {
113 bucket: "gonzalo".into(),
114 endpoint: Some("http://garage:3900".into()),
115 region: Some("garage".into()),
116 }
117 );
118 }
119
120 #[test]
121 fn s3_endpoint_and_region_optional() {
122 let cfg =
123 StoreConfig::from_env(env(&[("GONZALO_STORE", "s3"), ("GONZALO_S3_BUCKET", "b")]))
124 .unwrap();
125 assert_eq!(
126 cfg,
127 StoreConfig::S3 {
128 bucket: "b".into(),
129 endpoint: None,
130 region: None,
131 }
132 );
133 }
134
135 #[test]
136 fn unknown_store_is_an_error() {
137 let err = StoreConfig::from_env(env(&[("GONZALO_STORE", "cassandra")])).unwrap_err();
138 assert!(err.contains("cassandra"), "got {err}");
139 }
140}