use std::fmt;
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct EnvConfigFiles {
pub(crate) files: Vec<EnvConfigFile>,
}
impl EnvConfigFiles {
pub fn builder() -> Builder {
Builder::new()
}
}
impl Default for EnvConfigFiles {
fn default() -> Self {
Self {
files: vec![
EnvConfigFile::Default(EnvConfigFileKind::Config),
EnvConfigFile::Default(EnvConfigFileKind::Credentials),
],
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum EnvConfigFileKind {
Config,
Credentials,
}
impl EnvConfigFileKind {
pub(crate) fn default_path(&self) -> &'static str {
match &self {
EnvConfigFileKind::Credentials => "~/.aws/credentials",
EnvConfigFileKind::Config => "~/.aws/config",
}
}
pub(crate) fn override_environment_variable(&self) -> &'static str {
match &self {
EnvConfigFileKind::Config => "AWS_CONFIG_FILE",
EnvConfigFileKind::Credentials => "AWS_SHARED_CREDENTIALS_FILE",
}
}
}
#[derive(Clone)]
pub(crate) enum EnvConfigFile {
Default(EnvConfigFileKind),
FilePath {
kind: EnvConfigFileKind,
path: PathBuf,
},
FileContents {
kind: EnvConfigFileKind,
contents: String,
},
}
impl fmt::Debug for EnvConfigFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Default(kind) => f.debug_tuple("Default").field(kind).finish(),
Self::FilePath { kind, path } => f
.debug_struct("FilePath")
.field("kind", kind)
.field("path", path)
.finish(),
Self::FileContents { kind, contents: _ } => f
.debug_struct("FileContents")
.field("kind", kind)
.field("contents", &"** redacted **")
.finish(),
}
}
}
#[derive(Clone, Default, Debug)]
pub struct Builder {
with_config: bool,
with_credentials: bool,
custom_sources: Vec<EnvConfigFile>,
}
impl Builder {
pub fn new() -> Self {
Default::default()
}
pub fn include_default_config_file(mut self, include_default_config_file: bool) -> Self {
self.with_config = include_default_config_file;
self
}
pub fn include_default_credentials_file(
mut self,
include_default_credentials_file: bool,
) -> Self {
self.with_credentials = include_default_credentials_file;
self
}
pub fn with_file(mut self, kind: EnvConfigFileKind, file: impl Into<PathBuf>) -> Self {
self.custom_sources.push(EnvConfigFile::FilePath {
kind,
path: file.into(),
});
self
}
pub fn with_contents(mut self, kind: EnvConfigFileKind, contents: impl Into<String>) -> Self {
self.custom_sources.push(EnvConfigFile::FileContents {
kind,
contents: contents.into(),
});
self
}
pub fn build(self) -> EnvConfigFiles {
let mut files = self.custom_sources;
if self.with_credentials {
files.insert(0, EnvConfigFile::Default(EnvConfigFileKind::Credentials));
}
if self.with_config {
files.insert(0, EnvConfigFile::Default(EnvConfigFileKind::Config));
}
if files.is_empty() {
panic!("At least one profile file must be included in the `EnvConfigFiles` file set.");
}
EnvConfigFiles { files }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redact_file_contents_in_profile_file_debug() {
let shared_config_file = EnvConfigFile::FileContents {
kind: EnvConfigFileKind::Config,
contents: "sensitive_contents".into(),
};
let debug = format!("{shared_config_file:?}");
assert!(!debug.contains("sensitive_contents"));
assert!(debug.contains("** redacted **"));
}
#[test]
fn build_correctly_orders_default_config_credentials() {
let shared_config_files = EnvConfigFiles::builder()
.with_file(EnvConfigFileKind::Config, "foo")
.include_default_credentials_file(true)
.include_default_config_file(true)
.build();
assert_eq!(3, shared_config_files.files.len());
assert!(matches!(
shared_config_files.files[0],
EnvConfigFile::Default(EnvConfigFileKind::Config)
));
assert!(matches!(
shared_config_files.files[1],
EnvConfigFile::Default(EnvConfigFileKind::Credentials)
));
assert!(matches!(
shared_config_files.files[2],
EnvConfigFile::FilePath {
kind: EnvConfigFileKind::Config,
path: _
}
));
}
#[test]
#[should_panic]
fn empty_builder_panics() {
EnvConfigFiles::builder().build();
}
}