1#[derive(Debug, Clone)]
11pub struct S3Target {
12 pub endpoint: String,
13 pub bucket: String,
14 pub access_key: String,
15 pub secret_key: String,
16 pub region: Option<String>,
17}
18
19impl S3Target {
20 pub fn from_env(get: impl Fn(&str) -> Option<String>) -> Option<Self> {
23 Some(Self {
24 endpoint: non_empty(get("GONZALO_S3_TEST_ENDPOINT"))?,
25 bucket: non_empty(get("GONZALO_S3_TEST_BUCKET"))?,
26 access_key: non_empty(get("AWS_ACCESS_KEY_ID"))?,
27 secret_key: non_empty(get("AWS_SECRET_ACCESS_KEY"))?,
28 region: non_empty(get("GONZALO_S3_TEST_REGION")),
29 })
30 }
31
32 pub fn from_process_env() -> Option<Self> {
34 Self::from_env(|k| std::env::var(k).ok())
35 }
36}
37
38fn non_empty(v: Option<String>) -> Option<String> {
39 v.filter(|s| !s.trim().is_empty())
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use std::collections::HashMap;
46
47 fn env(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
48 let map: HashMap<String, String> = pairs
49 .iter()
50 .map(|(k, v)| (k.to_string(), v.to_string()))
51 .collect();
52 move |k: &str| map.get(k).cloned()
53 }
54
55 #[test]
56 fn resolves_when_all_required_present() {
57 let t = S3Target::from_env(env(&[
58 ("GONZALO_S3_TEST_ENDPOINT", "http://127.0.0.1:3900"),
59 ("GONZALO_S3_TEST_BUCKET", "soak"),
60 ("AWS_ACCESS_KEY_ID", "AK"),
61 ("AWS_SECRET_ACCESS_KEY", "SK"),
62 ]))
63 .expect("all required present");
64 assert_eq!(t.bucket, "soak");
65 assert_eq!(t.region, None);
66 }
67
68 #[test]
69 fn skips_when_endpoint_missing() {
70 assert!(
71 S3Target::from_env(env(&[
72 ("GONZALO_S3_TEST_BUCKET", "soak"),
73 ("AWS_ACCESS_KEY_ID", "AK"),
74 ("AWS_SECRET_ACCESS_KEY", "SK"),
75 ]))
76 .is_none()
77 );
78 }
79
80 #[test]
81 fn skips_when_credentials_missing() {
82 assert!(
83 S3Target::from_env(env(&[
84 ("GONZALO_S3_TEST_ENDPOINT", "http://127.0.0.1:3900"),
85 ("GONZALO_S3_TEST_BUCKET", "soak"),
86 ]))
87 .is_none()
88 );
89 }
90
91 #[test]
92 fn treats_blank_as_unset() {
93 assert!(
94 S3Target::from_env(env(&[
95 ("GONZALO_S3_TEST_ENDPOINT", " "),
96 ("GONZALO_S3_TEST_BUCKET", "soak"),
97 ("AWS_ACCESS_KEY_ID", "AK"),
98 ("AWS_SECRET_ACCESS_KEY", "SK"),
99 ]))
100 .is_none()
101 );
102 }
103
104 #[test]
105 fn region_is_optional_but_carried() {
106 let t = S3Target::from_env(env(&[
107 ("GONZALO_S3_TEST_ENDPOINT", "http://127.0.0.1:3900"),
108 ("GONZALO_S3_TEST_BUCKET", "soak"),
109 ("AWS_ACCESS_KEY_ID", "AK"),
110 ("AWS_SECRET_ACCESS_KEY", "SK"),
111 ("GONZALO_S3_TEST_REGION", "garage"),
112 ]))
113 .unwrap();
114 assert_eq!(t.region.as_deref(), Some("garage"));
115 }
116}