kellnr_settings/
settings.rs

1use std::convert::TryFrom;
2use std::env;
3use std::path::{Path, PathBuf};
4
5use config::{Config, ConfigError, Environment, File};
6use serde::{Deserialize, Serialize};
7
8use crate::compile_time_config;
9use crate::docs::Docs;
10use crate::local::Local;
11use crate::log::Log;
12use crate::origin::Origin;
13use crate::postgresql::Postgresql;
14use crate::proxy::Proxy;
15use crate::registry::Registry;
16use crate::s3::S3;
17use crate::setup::Setup;
18
19#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Default, Clone)]
20#[serde(default)]
21pub struct Settings {
22    pub setup: Setup,
23    pub registry: Registry,
24    pub docs: Docs,
25    pub proxy: Proxy,
26    pub log: Log,
27    pub local: Local,
28    pub origin: Origin,
29    pub postgresql: Postgresql,
30    pub s3: S3,
31}
32
33impl TryFrom<Option<&Path>> for Settings {
34    type Error = ConfigError;
35
36    fn try_from(config_path: Option<&Path>) -> Result<Self, Self::Error> {
37        let env = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
38        let builder = Config::builder();
39
40        // Add configuration values from a config file if a config path is provided
41        let builder = match config_path {
42            Some(config_path) => {
43                let default_file = Settings::join_path(config_path, "default")?;
44                let env_file = Settings::join_path(config_path, &env)?;
45                let local_file = Settings::join_path(config_path, "local")?;
46
47                builder
48                    // Start off by merging in the "default" configuration file
49                    .add_source(File::with_name(&default_file).required(false))
50                    // Add in the current environment file
51                    // Default to 'development' env
52                    // Note that this file is _optional_
53                    .add_source(File::with_name(&env_file).required(false))
54                    // Add in a local configuration file
55                    // This file shouldn't be checked in to git
56                    .add_source(File::with_name(&local_file).required(false))
57            }
58            None => builder,
59        };
60
61        let s = builder
62            // Add in settings from the environment (with a prefix of KELLNR)
63            .add_source(
64                Environment::with_prefix("KELLNR")
65                    .list_separator(",")
66                    .with_list_parse_key("registry.required_crate_fields")
67                    .try_parsing(true)
68                    .prefix_separator("_")
69                    .separator("__"),
70            )
71            .build()?;
72
73        // You can deserialize (and thus freeze) the entire configuration as
74        s.try_deserialize()
75    }
76}
77
78impl Settings {
79    fn join_path(config_path: &Path, file: &str) -> Result<String, ConfigError> {
80        config_path
81            .join(file)
82            .to_str()
83            .map(ToString::to_string)
84            .ok_or_else(|| ConfigError::Message("Invalid UTF-8 string".to_string()))
85    }
86
87    pub fn bin_path(&self) -> PathBuf {
88        PathBuf::from(&self.registry.data_dir).join("crates")
89    }
90
91    pub fn doc_queue_path(&self) -> PathBuf {
92        PathBuf::from(&self.registry.data_dir).join("doc_queue")
93    }
94
95    pub fn sqlite_path(&self) -> PathBuf {
96        PathBuf::from(&self.registry.data_dir).join("db.sqlite")
97    }
98
99    pub fn docs_path(&self) -> PathBuf {
100        PathBuf::from(&self.registry.data_dir).join("docs")
101    }
102
103    pub fn base_path(&self) -> PathBuf {
104        PathBuf::from(&self.registry.data_dir).join("git")
105    }
106
107    pub fn crates_io_bin_path(&self) -> PathBuf {
108        PathBuf::from(&self.registry.data_dir).join("cratesio")
109    }
110
111    pub fn crates_io_path(&self) -> String {
112        format!("{}/cratesio", self.registry.data_dir)
113    }
114
115    pub fn crates_path(&self) -> String {
116        format!("{}/crates", self.registry.data_dir)
117    }
118
119    pub fn crates_path_or_bucket(&self) -> String {
120        if self.s3.enabled
121            && let Some(bucket) = &self.s3.crates_bucket
122        {
123            bucket.clone()
124        } else {
125            self.crates_path()
126        }
127    }
128
129    pub fn crates_io_path_or_bucket(&self) -> String {
130        if self.s3.enabled
131            && let Some(bucket) = &self.s3.cratesio_bucket
132        {
133            bucket.clone()
134        } else {
135            self.crates_io_path()
136        }
137    }
138}
139
140pub fn get_settings() -> Result<Settings, ConfigError> {
141    let path = if Path::new(compile_time_config::KELLNR_CONFIG_DIR).exists() {
142        Some(Path::new(compile_time_config::KELLNR_CONFIG_DIR))
143    } else if Path::new("./config").exists() {
144        Some(Path::new("./config"))
145    } else if Path::new("../config").exists() {
146        Some(Path::new("../config"))
147    } else if Path::new("../../config").exists() {
148        Some(Path::new("../../config"))
149    } else {
150        None
151    };
152
153    Settings::try_from(path)
154}
155
156/// Used in Unit and Integration tests to provide test settings
157pub 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}