kellnr_settings/
settings.rs1use std::path::{Path, PathBuf};
2
3pub use provcfg::Error as SettingsError;
8use provcfg::{ClapArgs, Configurable, Provenance};
9use serde::{Deserialize, Serialize};
10
11use crate::config_source::SourceMap;
12use crate::docs::{Docs, DocsArgs, DocsPartial, DocsProv};
13use crate::gcs::{Gcs, GcsArgs, GcsPartial, GcsProv};
14use crate::local::{Local, LocalArgs, LocalPartial, LocalProv};
15use crate::log::{Log, LogArgs, LogPartial, LogProv};
16use crate::oauth2::{OAuth2, OAuth2Args, OAuth2Partial, OAuth2Prov};
17use crate::origin::{Origin, OriginArgs, OriginPartial, OriginProv};
18use crate::postgresql::{Postgresql, PostgresqlArgs, PostgresqlPartial, PostgresqlProv};
19use crate::proxy::{Proxy, ProxyArgs, ProxyPartial, ProxyProv};
20use crate::registry::{Registry, RegistryArgs, RegistryPartial, RegistryProv};
21use crate::s3::{S3, S3Args, S3Partial, S3Prov};
22use crate::setup::{Setup, SetupArgs, SetupPartial, SetupProv};
23use crate::toolchain::{Toolchain, ToolchainArgs, ToolchainPartial, ToolchainProv};
24
25#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs)]
30#[serde(default)]
31pub struct Settings {
32 #[configurable(nested)]
33 pub setup: Setup,
34 #[configurable(nested)]
35 pub registry: Registry,
36 #[configurable(nested)]
37 pub docs: Docs,
38 #[configurable(nested)]
39 pub proxy: Proxy,
40 #[configurable(nested)]
41 pub log: Log,
42 #[configurable(nested)]
43 pub local: Local,
44 #[configurable(nested)]
45 pub origin: Origin,
46 #[configurable(nested)]
47 pub postgresql: Postgresql,
48 #[configurable(nested)]
49 pub s3: S3,
50 #[configurable(nested)]
51 pub gcs: Gcs,
52 #[configurable(nested)]
53 pub oauth2: OAuth2,
54 #[configurable(nested)]
55 pub toolchain: Toolchain,
56}
57
58pub fn build_prov_with_cli(
67 config_file: Option<&Path>,
68 cli_partial: Option<SettingsPartial>,
69) -> Result<SettingsProv, SettingsError> {
70 let mut cfg = provcfg::Config::new();
71 if let Some(path) = config_file {
72 cfg = cfg.add_toml_file(path)?;
73 }
74 cfg = cfg.add_env_with_list_keys("KELLNR", env_list_keys().iter().copied());
75 if let Some(partial) = cli_partial {
76 cfg = cfg.add_cli(partial);
77 }
78 cfg.build::<SettingsProv>()
79}
80
81pub(crate) fn env_list_keys() -> &'static [&'static str] {
85 &[
86 "registry.required_crate_fields",
87 "oauth2.scopes",
88 "oauth2.additional_audiences",
89 ]
90}
91
92pub fn sources_from_prov(prov: &SettingsProv) -> SourceMap {
95 prov.sources_map()
96 .into_iter()
97 .map(|(key, category)| (key, category.into()))
98 .collect()
99}
100
101impl Settings {
102 pub fn bin_path(&self) -> PathBuf {
103 PathBuf::from(&self.registry.data_dir).join("crates")
104 }
105
106 pub fn doc_queue_path(&self) -> PathBuf {
107 PathBuf::from(&self.registry.data_dir).join("doc_queue")
108 }
109
110 pub fn sqlite_path(&self) -> PathBuf {
111 PathBuf::from(&self.registry.data_dir).join("db.sqlite")
112 }
113
114 pub fn docs_path(&self) -> String {
115 format!("{}/docs", self.registry.data_dir)
116 }
117
118 pub fn base_path(&self) -> PathBuf {
119 PathBuf::from(&self.registry.data_dir).join("git")
120 }
121
122 pub fn crates_io_bin_path(&self) -> PathBuf {
123 PathBuf::from(&self.registry.data_dir).join("cratesio")
124 }
125
126 pub fn crates_io_path(&self) -> String {
127 format!("{}/cratesio", self.registry.data_dir)
128 }
129
130 pub fn crates_path(&self) -> String {
131 format!("{}/crates", self.registry.data_dir)
132 }
133
134 pub fn crates_path_or_bucket(&self) -> String {
135 if self.s3.enabled {
136 self.s3.crates_bucket.clone()
137 } else if self.gcs.enabled {
138 self.gcs.crates_bucket.clone()
139 } else {
140 self.crates_path()
141 }
142 }
143
144 pub fn crates_io_path_or_bucket(&self) -> String {
145 if self.s3.enabled {
146 self.s3.cratesio_bucket.clone()
147 } else if self.gcs.enabled {
148 self.gcs.cratesio_bucket.clone()
149 } else {
150 self.crates_io_path()
151 }
152 }
153
154 pub fn toolchain_path(&self) -> String {
155 format!("{}/toolchains", self.registry.data_dir)
156 }
157
158 pub fn toolchain_path_or_bucket(&self) -> String {
159 if self.s3.enabled {
160 self.s3.toolchain_bucket.clone()
161 } else if self.gcs.enabled {
162 self.gcs.toolchain_bucket.clone()
163 } else {
164 self.toolchain_path()
165 }
166 }
167
168 pub fn docs_backend_is_local(&self) -> bool {
173 !(self.s3.enabled && self.s3.docs_bucket.is_some()
174 || self.gcs.enabled && self.gcs.docs_bucket.is_some())
175 }
176}
177
178pub fn test_settings() -> Settings {
180 Settings {
181 registry: Registry {
182 data_dir: "/tmp/kdata_test".to_string(),
183 ..Registry::default()
184 },
185 ..Settings::default()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use provcfg::Config;
192
193 use super::*;
194 use crate::config_source::ConfigSource;
195
196 #[test]
197 fn sources_from_prov_with_no_sources_marks_every_leaf_default() {
198 let prov = Config::new().build::<SettingsProv>().unwrap();
199 let map = sources_from_prov(&prov);
200
201 assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Default));
203 assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Default));
204 assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
205 assert!(
206 map.values().all(|s| *s == ConfigSource::Default),
207 "every entry must report Default when no sources were added"
208 );
209 }
210
211 #[test]
212 fn sources_from_prov_attributes_toml_leaves_to_toml_source() {
213 let prov = Config::new()
214 .add_toml_str(
215 "test.toml",
216 "[registry]\ndata_dir = \"/tmp/x\"\n[docs]\nenabled = true\n",
217 )
218 .build::<SettingsProv>()
219 .unwrap();
220 let map = sources_from_prov(&prov);
221
222 assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Toml));
223 assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Toml));
224 assert_eq!(map.get("registry.cache_size"), Some(&ConfigSource::Default));
227 assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
228 }
229}