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