use std::path::{Path, PathBuf};
pub use provcfg::Error as SettingsError;
use provcfg::{ClapArgs, Configurable, Provenance};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::config_source::SourceMap;
use crate::docs::{Docs, DocsArgs, DocsPartial, DocsProv};
use crate::filter::{
CratesioFilter, CratesioFilterArgs, CratesioFilterPartial, CratesioFilterProv, RegistryFilter,
RegistryFilterArgs, RegistryFilterPartial, RegistryFilterProv,
};
use crate::gcs::{Gcs, GcsArgs, GcsPartial, GcsProv};
use crate::local::{Local, LocalArgs, LocalPartial, LocalProv};
use crate::log::{Log, LogArgs, LogPartial, LogProv};
use crate::oauth2::{OAuth2, OAuth2Args, OAuth2Partial, OAuth2Prov};
use crate::origin::{Origin, OriginArgs, OriginPartial, OriginProv};
use crate::postgresql::{Postgresql, PostgresqlArgs, PostgresqlPartial, PostgresqlProv};
use crate::proxy::{Proxy, ProxyArgs, ProxyPartial, ProxyProv};
use crate::registry::{Registry, RegistryArgs, RegistryPartial, RegistryProv};
use crate::s3::{S3, S3Args, S3Partial, S3Prov};
use crate::setup::{Setup, SetupArgs, SetupPartial, SetupProv};
use crate::toolchain::{Toolchain, ToolchainArgs, ToolchainPartial, ToolchainProv};
use crate::webhooks::{Webhooks, WebhooksArgs, WebhooksPartial, WebhooksProv};
#[derive(
Debug, Default, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs, ToSchema,
)]
#[serde(default)]
pub struct Settings {
#[configurable(nested)]
pub setup: Setup,
#[configurable(nested)]
pub registry: Registry,
#[configurable(nested)]
pub docs: Docs,
#[configurable(nested)]
pub proxy: Proxy,
#[configurable(nested)]
pub log: Log,
#[configurable(nested)]
pub local: Local,
#[configurable(nested)]
pub origin: Origin,
#[configurable(nested)]
pub postgresql: Postgresql,
#[configurable(nested)]
pub s3: S3,
#[configurable(nested)]
pub gcs: Gcs,
#[configurable(nested)]
pub oauth2: OAuth2,
#[configurable(nested)]
pub toolchain: Toolchain,
#[configurable(nested)]
pub webhooks: Webhooks,
#[configurable(nested)]
pub cratesio_filter: CratesioFilter,
#[configurable(nested)]
pub registry_filter: RegistryFilter,
}
pub fn build_prov_with_cli(
config_file: Option<&Path>,
cli_partial: Option<SettingsPartial>,
) -> Result<SettingsProv, SettingsError> {
let mut cfg = provcfg::Config::new();
if let Some(path) = config_file {
cfg = cfg.add_toml_file(path)?;
}
cfg = cfg.add_env_with_list_keys("KELLNR", env_list_keys().iter().copied());
if let Some(partial) = cli_partial {
cfg = cfg.add_cli(partial);
}
cfg.build::<SettingsProv>()
}
pub fn settings_from_prov(prov: &SettingsProv) -> Settings {
let mut settings = Settings::from(prov);
settings.normalize();
settings
}
pub(crate) fn env_list_keys() -> &'static [&'static str] {
&[
"registry.required_crate_fields",
"oauth2.scopes",
"oauth2.additional_audiences",
"docs.build_wrapper",
"cratesio_filter.license_allowed",
"cratesio_filter.license_exempt_crates",
"registry_filter.license_allowed",
"registry_filter.license_exempt_crates",
]
}
pub fn sources_from_prov(prov: &SettingsProv) -> SourceMap {
prov.sources_map()
.into_iter()
.map(|(key, category)| (key, category.into()))
.collect()
}
impl Settings {
pub fn normalize(&mut self) {
self.origin.normalize();
self.proxy.normalize();
}
pub fn bin_path(&self) -> PathBuf {
PathBuf::from(&self.registry.data_dir).join("crates")
}
pub fn doc_queue_path(&self) -> PathBuf {
PathBuf::from(&self.registry.data_dir).join("doc_queue")
}
pub fn sqlite_path(&self) -> PathBuf {
PathBuf::from(&self.registry.data_dir).join("db.sqlite")
}
pub fn docs_path(&self) -> String {
format!("{}/docs", self.registry.data_dir)
}
pub fn base_path(&self) -> PathBuf {
PathBuf::from(&self.registry.data_dir).join("git")
}
pub fn crates_io_bin_path(&self) -> PathBuf {
PathBuf::from(&self.registry.data_dir).join("cratesio")
}
pub fn crates_io_path(&self) -> String {
format!("{}/cratesio", self.registry.data_dir)
}
pub fn crates_path(&self) -> String {
format!("{}/crates", self.registry.data_dir)
}
pub fn crates_path_or_bucket(&self) -> String {
if self.s3.enabled {
self.s3.crates_bucket.clone()
} else if self.gcs.enabled {
self.gcs.crates_bucket.clone()
} else {
self.crates_path()
}
}
pub fn crates_io_path_or_bucket(&self) -> String {
if self.s3.enabled {
self.s3.cratesio_bucket.clone()
} else if self.gcs.enabled {
self.gcs.cratesio_bucket.clone()
} else {
self.crates_io_path()
}
}
pub fn toolchain_path(&self) -> String {
format!("{}/toolchains", self.registry.data_dir)
}
pub fn toolchain_path_or_bucket(&self) -> String {
if self.s3.enabled {
self.s3.toolchain_bucket.clone()
} else if self.gcs.enabled {
self.gcs.toolchain_bucket.clone()
} else {
self.toolchain_path()
}
}
pub fn docs_backend_is_local(&self) -> bool {
!(self.s3.enabled && self.s3.docs_bucket.is_some()
|| self.gcs.enabled && self.gcs.docs_bucket.is_some())
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn test_settings() -> Settings {
test_settings_in(Path::new("/dev/null/kellnr-test"))
}
#[cfg(any(test, feature = "test-support"))]
pub fn test_settings_in(data_dir: &Path) -> Settings {
Settings {
registry: Registry {
data_dir: data_dir.to_string_lossy().into_owned(),
..Registry::default()
},
..Settings::default()
}
}
#[cfg(test)]
mod tests {
use provcfg::Config;
use super::*;
use crate::config_source::ConfigSource;
#[test]
fn sources_from_prov_with_no_sources_marks_every_leaf_default() {
let prov = Config::new().build::<SettingsProv>().unwrap();
let map = sources_from_prov(&prov);
assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Default));
assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Default));
assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
assert!(
map.values().all(|s| *s == ConfigSource::Default),
"every entry must report Default when no sources were added"
);
}
#[test]
fn sources_from_prov_attributes_toml_leaves_to_toml_source() {
let prov = Config::new()
.add_toml_str(
"test.toml",
"[registry]\ndata_dir = \"/tmp/x\"\n[docs]\nenabled = true\n",
)
.build::<SettingsProv>()
.unwrap();
let map = sources_from_prov(&prov);
assert_eq!(map.get("registry.data_dir"), Some(&ConfigSource::Toml));
assert_eq!(map.get("docs.enabled"), Some(&ConfigSource::Toml));
assert_eq!(map.get("registry.cache_size"), Some(&ConfigSource::Default));
assert_eq!(map.get("s3.enabled"), Some(&ConfigSource::Default));
}
#[test]
fn settings_from_prov_normalizes_origin_path() {
let prov = Config::new()
.add_toml_str("test.toml", "[origin]\npath = \"registry/\"\n")
.build::<SettingsProv>()
.unwrap();
assert_eq!(settings_from_prov(&prov).origin.path, "/registry");
}
#[test]
fn settings_from_prov_normalizes_proxy_urls() {
let prov = Config::new()
.add_toml_str(
"test.toml",
"[proxy]\nurl = \"https://mirror.example.com/crates\"\n",
)
.build::<SettingsProv>()
.unwrap();
assert_eq!(
settings_from_prov(&prov).proxy.url.as_str(),
"https://mirror.example.com/crates/"
);
}
}