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::filter::{
14 CratesioFilter, CratesioFilterArgs, CratesioFilterPartial, CratesioFilterProv, RegistryFilter,
15 RegistryFilterArgs, RegistryFilterPartial, RegistryFilterProv,
16};
17use crate::gcs::{Gcs, GcsArgs, GcsPartial, GcsProv};
18use crate::local::{Local, LocalArgs, LocalPartial, LocalProv};
19use crate::log::{Log, LogArgs, LogPartial, LogProv};
20use crate::oauth2::{OAuth2, OAuth2Args, OAuth2Partial, OAuth2Prov};
21use crate::origin::{Origin, OriginArgs, OriginPartial, OriginProv};
22use crate::postgresql::{Postgresql, PostgresqlArgs, PostgresqlPartial, PostgresqlProv};
23use crate::proxy::{Proxy, ProxyArgs, ProxyPartial, ProxyProv};
24use crate::registry::{Registry, RegistryArgs, RegistryPartial, RegistryProv};
25use crate::s3::{S3, S3Args, S3Partial, S3Prov};
26use crate::setup::{Setup, SetupArgs, SetupPartial, SetupProv};
27use crate::toolchain::{Toolchain, ToolchainArgs, ToolchainPartial, ToolchainProv};
28use crate::webhooks::{Webhooks, WebhooksArgs, WebhooksPartial, WebhooksProv};
29
30#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs)]
35#[serde(default)]
36pub struct Settings {
37 #[configurable(nested)]
38 pub setup: Setup,
39 #[configurable(nested)]
40 pub registry: Registry,
41 #[configurable(nested)]
42 pub docs: Docs,
43 #[configurable(nested)]
44 pub proxy: Proxy,
45 #[configurable(nested)]
46 pub log: Log,
47 #[configurable(nested)]
48 pub local: Local,
49 #[configurable(nested)]
50 pub origin: Origin,
51 #[configurable(nested)]
52 pub postgresql: Postgresql,
53 #[configurable(nested)]
54 pub s3: S3,
55 #[configurable(nested)]
56 pub gcs: Gcs,
57 #[configurable(nested)]
58 pub oauth2: OAuth2,
59 #[configurable(nested)]
60 pub toolchain: Toolchain,
61 #[configurable(nested)]
62 pub webhooks: Webhooks,
63 #[configurable(nested)]
64 pub cratesio_filter: CratesioFilter,
65 #[configurable(nested)]
66 pub registry_filter: RegistryFilter,
67}
68
69pub fn build_prov_with_cli(
78 config_file: Option<&Path>,
79 cli_partial: Option<SettingsPartial>,
80) -> Result<SettingsProv, SettingsError> {
81 let mut cfg = provcfg::Config::new();
82 if let Some(path) = config_file {
83 cfg = cfg.add_toml_file(path)?;
84 }
85 cfg = cfg.add_env_with_list_keys("KELLNR", env_list_keys().iter().copied());
86 if let Some(partial) = cli_partial {
87 cfg = cfg.add_cli(partial);
88 }
89 cfg.build::<SettingsProv>()
90}
91
92pub(crate) fn env_list_keys() -> &'static [&'static str] {
96 &[
97 "registry.required_crate_fields",
98 "oauth2.scopes",
99 "oauth2.additional_audiences",
100 "docs.build_wrapper",
101 "cratesio_filter.license_allowed",
102 "cratesio_filter.license_exempt_crates",
103 "registry_filter.license_allowed",
104 "registry_filter.license_exempt_crates",
105 ]
106}
107
108pub fn sources_from_prov(prov: &SettingsProv) -> SourceMap {
111 prov.sources_map()
112 .into_iter()
113 .map(|(key, category)| (key, category.into()))
114 .collect()
115}
116
117impl Settings {
118 pub fn bin_path(&self) -> PathBuf {
119 PathBuf::from(&self.registry.data_dir).join("crates")
120 }
121
122 pub fn doc_queue_path(&self) -> PathBuf {
123 PathBuf::from(&self.registry.data_dir).join("doc_queue")
124 }
125
126 pub fn sqlite_path(&self) -> PathBuf {
127 PathBuf::from(&self.registry.data_dir).join("db.sqlite")
128 }
129
130 pub fn docs_path(&self) -> String {
131 format!("{}/docs", self.registry.data_dir)
132 }
133
134 pub fn base_path(&self) -> PathBuf {
135 PathBuf::from(&self.registry.data_dir).join("git")
136 }
137
138 pub fn crates_io_bin_path(&self) -> PathBuf {
139 PathBuf::from(&self.registry.data_dir).join("cratesio")
140 }
141
142 pub fn crates_io_path(&self) -> String {
143 format!("{}/cratesio", self.registry.data_dir)
144 }
145
146 pub fn crates_path(&self) -> String {
147 format!("{}/crates", self.registry.data_dir)
148 }
149
150 pub fn crates_path_or_bucket(&self) -> String {
151 if self.s3.enabled {
152 self.s3.crates_bucket.clone()
153 } else if self.gcs.enabled {
154 self.gcs.crates_bucket.clone()
155 } else {
156 self.crates_path()
157 }
158 }
159
160 pub fn crates_io_path_or_bucket(&self) -> String {
161 if self.s3.enabled {
162 self.s3.cratesio_bucket.clone()
163 } else if self.gcs.enabled {
164 self.gcs.cratesio_bucket.clone()
165 } else {
166 self.crates_io_path()
167 }
168 }
169
170 pub fn toolchain_path(&self) -> String {
171 format!("{}/toolchains", self.registry.data_dir)
172 }
173
174 pub fn toolchain_path_or_bucket(&self) -> String {
175 if self.s3.enabled {
176 self.s3.toolchain_bucket.clone()
177 } else if self.gcs.enabled {
178 self.gcs.toolchain_bucket.clone()
179 } else {
180 self.toolchain_path()
181 }
182 }
183
184 pub fn docs_backend_is_local(&self) -> bool {
189 !(self.s3.enabled && self.s3.docs_bucket.is_some()
190 || self.gcs.enabled && self.gcs.docs_bucket.is_some())
191 }
192}
193
194pub fn test_settings() -> Settings {
196 Settings {
197 registry: Registry {
198 data_dir: "/tmp/kdata_test".to_string(),
199 ..Registry::default()
200 },
201 ..Settings::default()
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use provcfg::Config;
208
209 use super::*;
210 use crate::config_source::ConfigSource;
211
212 #[test]
213 fn sources_from_prov_with_no_sources_marks_every_leaf_default() {
214 let prov = Config::new().build::<SettingsProv>().unwrap();
215 let map = sources_from_prov(&prov);
216
217 assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Default));
219 assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Default));
220 assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
221 assert!(
222 map.values().all(|s| *s == ConfigSource::Default),
223 "every entry must report Default when no sources were added"
224 );
225 }
226
227 #[test]
228 fn sources_from_prov_attributes_toml_leaves_to_toml_source() {
229 let prov = Config::new()
230 .add_toml_str(
231 "test.toml",
232 "[registry]\ndata_dir = \"/tmp/x\"\n[docs]\nenabled = true\n",
233 )
234 .build::<SettingsProv>()
235 .unwrap();
236 let map = sources_from_prov(&prov);
237
238 assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Toml));
239 assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Toml));
240 assert_eq!(map.get("registry.cache_size"), Some(&ConfigSource::Default));
243 assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
244 }
245}