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