1
Fork 0
mirror of https://github.com/Steffo99/micronfig.git synced 2024-11-27 18:34:23 +00:00

Complete the library

This commit is contained in:
Steffo 2023-04-28 02:44:47 +02:00
parent 1a08c9638e
commit b31d6841ee
Signed by: steffo
GPG key ID: 2A24051445686895
4 changed files with 307 additions and 37 deletions

View file

@ -6,3 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
[dev-dependencies]
tempfile = "3.5.0"

91
src/file.rs Normal file
View file

@ -0,0 +1,91 @@
//! Module defining a function retrieving a configuration value from a file at a path specified in the environment.
/// Get a value of the requested type from the file at the path contained in the environment variable with the given name.
pub fn get<Key, Type>(name: Key) -> Result<Type>
where Key: AsRef<std::ffi::OsStr>,
Type: std::str::FromStr,
{
let path = std::env::var(name)
.map_err(Error::CannotReadEnvVar)?;
let path = std::ffi::OsString::from(path);
let path = std::path::PathBuf::from(path);
let mut file = std::fs::File::open(path)
.map_err(Error::CannotOpenFile)?;
use std::io::Read;
let mut data = String::new();
file.read_to_string(&mut data)
.map_err(Error::CannotReadFile)?;
let value = Type::from_str(&data)
.map_err(Error::CannotConvertValue)?;
Ok(value)
}
/// A possible error encountered by [`get`].
#[derive(Debug)]
pub enum Error<ConversionError>
{
/// The environment variable could not be read.
CannotReadEnvVar(std::env::VarError),
/// The specified file could not be opened. (Probably it doesn't exist.)
CannotOpenFile(std::io::Error),
/// The specified file could not be read.
CannotReadFile(std::io::Error),
/// The value could not be converted to the desired type.
CannotConvertValue(ConversionError),
}
/// A possible error encountered by [`get`].
pub type Result<Type> = std::result::Result<Type, Error<<Type as std::str::FromStr>::Err>>;
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::tempfile_fixture;
#[test]
fn it_works() {
let file = tempfile_fixture("1");
std::env::set_var("NUMBER_FILE", file.as_os_str());
let number = get::<&str, u32>("NUMBER_FILE").unwrap();
assert_eq!(number, 1u32);
}
#[test]
fn missing_envvar() {
match get::<&str, String>("THIS_ENVVAR_DOES_NOT_EXIST_FILE") {
Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent)) => {},
_ => panic!("expected Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent))"),
}
}
#[test]
fn missing_file() {
std::env::set_var("NUMBER_FILE", "/this/file/does/not/exist");
match get::<&str, u32>("NUMBER_FILE") {
Err(Error::CannotOpenFile(_)) => {},
_ => panic!("expected Err(Error::CannotOpenFile(_))"),
}
}
#[test]
fn not_a_number() {
let file = tempfile_fixture("XYZ");
std::env::set_var("NUMBER_FILE", file.as_os_str());
match get::<&str, u32>("NUMBER_FILE") {
Err(Error::CannotConvertValue(_)) => {},
_ => panic!("expected Err(Error::CannotConvertValue(_))"),
}
}
}

View file

@ -1,50 +1,163 @@
use std::env; //! A tiny crate for [twelve-factor app configuration](https://12factor.net/config).
use std::ffi::OsString;
use std::io::Read;
use std::path::PathBuf;
use std::fs::File;
/// An error that occurred while getting a configuration value. pub mod var;
pub enum GetFileError<TargetType> where TargetType: TryFrom<String> { pub mod file;
CannotReadEnvVar(env::VarError),
CannotOpenFile(std::io::Error),
CannotReadFile(std::io::Error), /// Get a value of the requested type, trying the following sources in order:
CannotConvertValue(<TargetType as TryFrom<String>>::Error), ///
/// 1. the environment variable `{name}` (see [`var::get`])
/// 2. the contents of the file at the path specified at the environment variable `{name}_FILE` (see [`file::get`])
///
pub fn get<Type>(name: &str) -> Source<Type>
where Type: std::str::FromStr,
{
let v = var::get(name);
match v {
Err(var::Error::CannotReadEnvVar(_)) => {},
_ => return Source::Var(v),
} }
/// The result of an attempt at getting a configuration value. let v = file::get(format!("{name}_FILE"));
pub type GetFileResult<TargetType> = Result<TargetType, GetFileError<TargetType>>;
/// Get a configuration value from a file specified in the given environment variable. match v {
/// Err(file::Error::CannotReadEnvVar(_)) => {},
/// _ => return Source::File(v),
/// # Parameters }
///
/// - `name`: the name of the environment variable containing the location of the file containing the configuration value.
/// - `TargetType`: the struct the contents of the file are to be converted into using [`TryFrom`] [`String`].
///
pub fn get_file<TargetType>(name: &str) -> GetFileResult<TargetType> where TargetType: TryFrom<String> {
let path = env::var(name).map_err(GetFileError::CannotReadEnvVar)?;
let path = OsString::from(path);
let path = PathBuf::from(path);
let mut file = File::open(path).map_err(GetFileError::CannotOpenFile)?; Source::NotFound
}
let mut data = String::new();
file.read_to_string(&mut data).map_err(GetFileError::CannotReadFile)?;
let value = TargetType::try_from(data).map_err(GetFileError::CannotConvertValue)?; #[non_exhaustive]
pub enum Source<Type>
where Type: std::str::FromStr,
{
Var(var::Result<Type>),
File(file::Result<Type>),
NotFound,
}
Ok(value) impl<Type> Source<Type>
where Type: std::str::FromStr,
{
/// Like [`Result::expect`], but tries to access the nested [`Ok`] value.
pub fn expect(self, msg: &str) -> Type {
match self {
Self::Var(Ok(v)) => v,
Self::File(Ok(v)) => v,
_ => panic!("{}", msg),
}
}
/// Like [`Result::unwrap`], but tries to access the nested [`Ok`] value.
pub fn unwrap(self) -> Type {
self.expect("called `Source::unwrap()` on an `Err` or `NotFound` value")
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { pub(crate) mod tests {
use super::*; use super::*;
pub(crate) fn tempfile_fixture(content: &str) -> tempfile::TempPath {
use std::io::Write;
let mut file = tempfile::NamedTempFile::new()
.expect("the tempfile fixture to be created successfully");
write!(file, "{}", content)
.expect("to be able to write into the tempfile fixture");
file.into_temp_path()
}
#[test] #[test]
fn it_works() { fn it_works_var() {
todo!() std::env::set_var("NUMBER", "1");
std::env::remove_var("NUMBER_FILE");
match get::<u32>("NUMBER") {
Source::Var(Ok(1u32)) => {},
_ => panic!("expected Source::Var(Ok(1u32))")
}
}
#[test]
fn it_works_file() {
let file = tempfile_fixture("1");
std::env::remove_var("NUMBER");
std::env::set_var("NUMBER_FILE", file.as_os_str());
let n = get::<u32>("NUMBER");
match n {
Source::File(Ok(1u32)) => {},
_ => panic!("expected Source::File(Ok(1u32))")
}
}
#[test]
fn missing_envvar() {
match get::<String>("THIS_ENVVAR_DOES_NOT_EXIST") {
Source::NotFound => {},
_ => panic!("expected Source::NotFound"),
}
}
#[test]
fn missing_file() {
std::env::remove_var("NUMBER");
std::env::set_var("NUMBER_FILE", "/this/file/does/not/exist");
match get::<u32>("NUMBER") {
Source::File(Err(file::Error::CannotOpenFile(_))) => {},
_ => panic!("expected Source::File(Err(file::Error::CannotOpenFile(_)))"),
}
}
#[test]
fn not_a_number_var() {
std::env::set_var("NUMBER", "XYZ");
std::env::remove_var("NUMBER_FILE");
match get::<u32>("NUMBER") {
Source::Var(Err(var::Error::CannotConvertValue(_))) => {},
_ => panic!("expected Source::Var(Err(var::Error::CannotConvertValue(_)))"),
}
}
#[test]
fn not_a_number_file() {
let file = tempfile_fixture("XYZ");
std::env::set_var("NUMBER_FILE", file.as_os_str());
std::env::remove_var("NUMBER");
match get::<u32>("NUMBER") {
Source::File(Err(file::Error::CannotConvertValue(_))) => {},
_ => panic!("expected Source::File(Err(file::Error::CannotConvertValue(_)))"),
}
}
#[test]
fn unwrap_var_ok() {
Source::Var(Ok("ok".to_string())).unwrap();
}
#[test]
fn unwrap_file_ok() {
Source::File(Ok("ok".to_string())).unwrap();
}
#[test]
#[should_panic]
fn unwrap_var_err() {
Source::<String>::Var(Err(var::Error::CannotReadEnvVar(std::env::VarError::NotPresent))).unwrap();
}
#[test]
#[should_panic]
fn unwrap_file_err() {
Source::<String>::File(Err(file::Error::CannotReadEnvVar(std::env::VarError::NotPresent))).unwrap();
} }
} }

63
src/var.rs Normal file
View file

@ -0,0 +1,63 @@
//! Module defining a function retrieving a configuration value from the environment.
/// Get a value of the requested type from the environment variable with the given name.
pub fn get<Key, Type>(name: Key) -> Result<Type>
where Key: AsRef<std::ffi::OsStr>,
Type: std::str::FromStr,
{
let data = std::env::var(name)
.map_err(Error::CannotReadEnvVar)?;
let value = Type::from_str(&data)
.map_err(Error::CannotConvertValue)?;
Ok(value)
}
/// A possible error encountered by [`get`].
#[derive(Debug)]
pub enum Error<ConversionError> {
/// The environment variable could not be read.
CannotReadEnvVar(std::env::VarError),
/// The value could not be converted to the desired type.
CannotConvertValue(ConversionError),
}
/// The result of [`get`].
pub type Result<Type> = std::result::Result<Type, Error<<Type as std::str::FromStr>::Err>>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
std::env::set_var("NUMBER", "1");
let number = get::<&str, u32>("NUMBER").unwrap();
assert_eq!(number, 1u32);
}
#[test]
fn missing_envvar() {
match get::<&str, String>("THIS_ENVVAR_DOES_NOT_EXIST") {
Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent)) => {},
_ => panic!("expected Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent))"),
}
}
#[test]
fn not_a_number() {
std::env::set_var("NUMBER", "XYZ");
match get::<&str, u32>("NUMBER") {
Err(Error::CannotConvertValue(_)) => {},
_ => panic!("expected Error::CannotConvertValue(_)"),
}
}
}