1use 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};
24use crate::webhooks::{Webhooks, WebhooksArgs, WebhooksPartial, WebhooksProv};
25
26#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs)]
31#[serde(default)]
32pub struct Settings {
33 #[configurable(nested)]
34 pub setup: Setup,
35 #[configurable(nested)]
36 pub registry: Registry,
37 #[configurable(nested)]
38 pub docs: Docs,
39 #[configurable(nested)]
40 pub proxy: Proxy,
41 #[configurable(nested)]
42 pub log: Log,
43 #[configurable(nested)]
44 pub local: Local,
45 #[configurable(nested)]
46 pub origin: Origin,
47 #[configurable(nested)]
48 pub postgresql: Postgresql,
49 #[configurable(nested)]
50 pub s3: S3,
51 #[configurable(nested)]
52 pub gcs: Gcs,
53 #[configurable(nested)]
54 pub oauth2: OAuth2,
55 #[configurable(nested)]
56 pub toolchain: Toolchain,
57 #[configurable(nested)]
58 pub webhooks: Webhooks,
59}
60
61pub fn build_prov_with_cli(
70 config_file: Option<&Path>,
71 cli_partial: Option<SettingsPartial>,
72) -> Result<SettingsProv, SettingsError> {
73 let mut cfg = provcfg::Config::new();
74 if let Some(path) = config_file {
75 cfg = cfg.add_toml_file(path)?;
76 }
77 cfg = cfg.add_env_with_list_keys("KELLNR", env_list_keys().iter().copied());
78 if let Some(partial) = cli_partial {
79 cfg = cfg.add_cli(partial);
80 }
81 cfg.build::<SettingsProv>()
82}
83
84pub(crate) fn env_list_keys() -> &'static [&'static str] {
88 &[
89 "registry.required_crate_fields",
90 "oauth2.scopes",
91 "oauth2.additional_audiences",
92 "docs.build_wrapper",
93 ]
94}
95
96pub fn sources_from_prov(prov: &SettingsProv) -> SourceMap {
99 prov.sources_map()
100 .into_iter()
101 .map(|(key, category)| (key, category.into()))
102 .collect()
103}
104
105impl Settings {
106 pub fn bin_path(&self) -> PathBuf {
107 PathBuf::from(&self.registry.data_dir).join("crates")
108 }
109
110 pub fn doc_queue_path(&self) -> PathBuf {
111 PathBuf::from(&self.registry.data_dir).join("doc_queue")
112 }
113
114 pub fn sqlite_path(&self) -> PathBuf {
115 PathBuf::from(&self.registry.data_dir).join("db.sqlite")
116 }
117
118 pub fn docs_path(&self) -> String {
119 format!("{}/docs", self.registry.data_dir)
120 }
121
122 pub fn base_path(&self) -> PathBuf {
123 PathBuf::from(&self.registry.data_dir).join("git")
124 }
125
126 pub fn crates_io_bin_path(&self) -> PathBuf {
127 PathBuf::from(&self.registry.data_dir).join("cratesio")
128 }
129
130 pub fn crates_io_path(&self) -> String {
131 format!("{}/cratesio", self.registry.data_dir)
132 }
133
134 pub fn crates_path(&self) -> String {
135 format!("{}/crates", self.registry.data_dir)
136 }
137
138 pub fn crates_path_or_bucket(&self) -> String {
139 if self.s3.enabled {
140 self.s3.crates_bucket.clone()
141 } else if self.gcs.enabled {
142 self.gcs.crates_bucket.clone()
143 } else {
144 self.crates_path()
145 }
146 }
147
148 pub fn crates_io_path_or_bucket(&self) -> String {
149 if self.s3.enabled {
150 self.s3.cratesio_bucket.clone()
151 } else if self.gcs.enabled {
152 self.gcs.cratesio_bucket.clone()
153 } else {
154 self.crates_io_path()
155 }
156 }
157
158 pub fn toolchain_path(&self) -> String {
159 format!("{}/toolchains", self.registry.data_dir)
160 }
161
162 pub fn toolchain_path_or_bucket(&self) -> String {
163 if self.s3.enabled {
164 self.s3.toolchain_bucket.clone()
165 } else if self.gcs.enabled {
166 self.gcs.toolchain_bucket.clone()
167 } else {
168 self.toolchain_path()
169 }
170 }
171
172 pub fn docs_backend_is_local(&self) -> bool {
177 !(self.s3.enabled && self.s3.docs_bucket.is_some()
178 || self.gcs.enabled && self.gcs.docs_bucket.is_some())
179 }
180}
181
182pub fn test_settings() -> Settings {
184 Settings {
185 registry: Registry {
186 data_dir: "/tmp/kdata_test".to_string(),
187 ..Registry::default()
188 },
189 ..Settings::default()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use provcfg::Config;
196
197 use super::*;
198 use crate::config_source::ConfigSource;
199
200 #[test]
201 fn sources_from_prov_with_no_sources_marks_every_leaf_default() {
202 let prov = Config::new().build::<SettingsProv>().unwrap();
203 let map = sources_from_prov(&prov);
204
205 assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Default));
207 assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Default));
208 assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
209 assert!(
210 map.values().all(|s| *s == ConfigSource::Default),
211 "every entry must report Default when no sources were added"
212 );
213 }
214
215 #[test]
216 fn sources_from_prov_attributes_toml_leaves_to_toml_source() {
217 let prov = Config::new()
218 .add_toml_str(
219 "test.toml",
220 "[registry]\ndata_dir = \"/tmp/x\"\n[docs]\nenabled = true\n",
221 )
222 .build::<SettingsProv>()
223 .unwrap();
224 let map = sources_from_prov(&prov);
225
226 assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Toml));
227 assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Toml));
228 assert_eq!(map.get("registry.cache_size"), Some(&ConfigSource::Default));
231 assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
232 }
233}