1use std::fmt;
9
10use serde::Serialize;
11
12pub const TARGET: &str = "DRE_TARGET";
14pub const TARGET_PATH: &str = "DRE_TARGET_PATH";
16pub const PROFILES_DIR: &str = "DRE_PROFILES_DIR";
18pub const PLUGINS_DIR: &str = "DRE_PLUGINS_DIR";
20pub const REGISTRY_URL: &str = "DRE_REGISTRY_URL";
22pub const GITHUB_API_URL: &str = "DRE_GITHUB_API_URL";
24pub const TIMEZONE: &str = "DRE_TIMEZONE";
26pub const RUN_DATE: &str = "DRE_RUN_DATE";
28pub const RUN_AT: &str = "DRE_RUN_AT";
30pub const HTTP_TIMEOUT: &str = "DRE_HTTP_TIMEOUT";
32pub const RUN_TIMEOUT: &str = "DRE_RUN_TIMEOUT";
34pub const KEEP_RUNS: &str = "DRE_KEEP_RUNS";
36pub const THREADS: &str = "DRE_THREADS";
39
40pub fn env(name: &str) -> Option<String> {
42 std::env::var(name).ok().filter(|v| !v.is_empty())
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum Source {
48 Flag(&'static str),
50 Env(&'static str),
52 Project(&'static str),
54 Found(&'static str),
56 Default,
58}
59
60impl fmt::Display for Source {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 Source::Flag(name) | Source::Env(name) | Source::Found(name) => f.write_str(name),
64 Source::Project(key) => write!(f, "`{key}` in dre_project.yml"),
65 Source::Default => f.write_str("default"),
66 }
67 }
68}
69
70impl Serialize for Source {
71 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
72 s.collect_str(self)
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
78pub struct Setting {
79 pub name: &'static str,
80 pub value: Option<String>,
82 pub source: Source,
83}
84
85#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
87#[serde(transparent)]
88pub struct Settings(Vec<Setting>);
89
90impl Settings {
91 pub fn set(&mut self, name: &'static str, value: Option<String>, source: Source) {
93 self.0.retain(|s| s.name != name);
94 self.0.push(Setting { name, value, source });
95 }
96
97 pub fn get(&self, name: &str) -> Option<&Setting> {
98 self.0.iter().find(|s| s.name == name)
99 }
100
101 pub fn iter(&self) -> impl Iterator<Item = &Setting> {
102 self.0.iter()
103 }
104
105 pub fn lines(&self) -> Vec<String> {
107 self.0
108 .iter()
109 .map(|s| {
110 format!(
111 "{} = {} ({})",
112 s.name,
113 s.value.as_deref().unwrap_or("unset"),
114 s.source
115 )
116 })
117 .collect()
118 }
119}
120
121pub fn run_settings(timezone_flag: Option<&str>) -> Settings {
125 let mut s = Settings::default();
126 let (tz, src) = match flag_or_env(timezone_flag, "--timezone", TIMEZONE) {
127 Some((v, src)) => (Some(v), src),
128 None => (None, Source::Default),
129 };
130 s.set("timezone", tz, src);
131 for (name, var) in [("run_date", RUN_DATE), ("scheduled_at", RUN_AT)] {
132 match env(var) {
133 Some(v) => s.set(name, Some(v), Source::Env(var)),
134 None => s.set(name, None, Source::Default),
135 }
136 }
137 s
138}
139
140pub fn flag_or_env(
142 flag: Option<&str>,
143 flag_name: &'static str,
144 var: &'static str,
145) -> Option<(String, Source)> {
146 match flag.filter(|f| !f.is_empty()) {
147 Some(f) => Some((f.to_string(), Source::Flag(flag_name))),
148 None => env(var).map(|v| (v, Source::Env(var))),
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn a_flag_beats_the_environment() {
158 assert_eq!(
159 flag_or_env(Some("prod"), "--target", "DRE_TEST_SETTINGS_UNSET"),
160 Some(("prod".into(), Source::Flag("--target")))
161 );
162 assert_eq!(flag_or_env(None, "--target", "DRE_TEST_SETTINGS_UNSET"), None);
163 assert_eq!(flag_or_env(Some(""), "--target", "DRE_TEST_SETTINGS_UNSET"), None);
164 }
165
166 #[test]
167 fn settings_record_their_source_and_replace_earlier_values() {
168 let mut s = Settings::default();
169 s.set("target", Some("dev".into()), Source::Default);
170 s.set("target_path", None, Source::Default);
171 s.set("target", Some("prod".into()), Source::Env(TARGET));
172 assert_eq!(
173 s.lines(),
174 ["target_path = unset (default)", "target = prod (DRE_TARGET)"]
175 );
176 assert_eq!(
177 serde_json::to_value(&s).unwrap()[1],
178 serde_json::json!({"name": "target", "value": "prod", "source": "DRE_TARGET"})
179 );
180 }
181}