snyk_config/
lib.rs

1use std::env;
2use std::ffi::OsString;
3use std::io;
4use std::path::{Path, PathBuf};
5
6use serde_json::{Map, Value};
7use thiserror::Error;
8
9mod from_env;
10mod from_file;
11mod merge_sources;
12
13use merge_sources::merge_sources;
14
15pub type Json = Map<String, Value>;
16pub type Result<T> = std::result::Result<T, ConfigError>;
17
18pub struct Config {
19    pub prefix: String,
20    pub dir: PathBuf,
21    pub secrets_file: PathBuf,
22    pub service_env: OsString,
23    _use_default_default: (),
24}
25
26impl Default for Config {
27    fn default() -> Self {
28        Config {
29            prefix: "CONF_".to_string(),
30            dir: PathBuf::new(),
31            secrets_file: PathBuf::from(
32                env::var_os("CONFIG_SECRET_FILE")
33                    .unwrap_or_else(|| OsString::from("config.secret.json")),
34            ),
35            service_env: env::var_os("SERVICE_ENV").unwrap_or_else(|| OsString::from("local")),
36            _use_default_default: (),
37        }
38    }
39}
40
41#[derive(Debug, Error)]
42pub enum ConfigError {
43    #[error("invalid utf-8 in {key:?}, got roughly {value:?}")]
44    InvalidEnvEncoding { key: String, value: String },
45
46    #[error("locating file failed: {path:?} (in {cwd:?})")]
47    ResolvePath {
48        source: io::Error,
49        path: PathBuf,
50        cwd: io::Result<PathBuf>,
51    },
52
53    #[error("open {path:?} failed")]
54    FileOpenFailed { source: io::Error, path: PathBuf },
55
56    #[error("invalid json in  {path:?}")]
57    InvalidJson {
58        source: serde_json::Error,
59        path: PathBuf,
60    },
61}
62
63impl Config {
64    pub fn for_prefix<S: ToString>(prefix: S) -> Result<Json> {
65        Config {
66            prefix: prefix.to_string(),
67            ..Default::default()
68        }
69        .load()
70    }
71
72    pub fn for_dir<P: AsRef<Path>>(dir: P) -> Result<Json> {
73        Config {
74            dir: dir.as_ref().to_path_buf(),
75            secrets_file: join(
76                dir.as_ref().to_path_buf(),
77                &OsString::from("config.secret.json"),
78            ),
79            ..Default::default()
80        }
81        .load()
82    }
83
84    pub fn load(self) -> Result<Json> {
85        let default = from_file::load(join(
86            self.dir.to_path_buf(),
87            &OsString::from("config.default.json"),
88        ))?;
89        let service_env = from_file::load(join(self.dir, &env_file(&self.service_env)))?;
90        let secret = from_file::load(self.secrets_file)?;
91        let from_env = from_env::from_env(&self.prefix)?;
92        Ok(merge_sources(
93            default,
94            &[service_env, secret, Value::Object(from_env)],
95        ))
96    }
97}
98
99fn join(mut root: PathBuf, extra: &OsString) -> PathBuf {
100    root.push(extra);
101    root
102}
103
104fn env_file(env: &OsString) -> OsString {
105    let mut file = OsString::from("config.");
106    file.push(env);
107    file.push(".json");
108    file
109}