mirror of
https://github.com/Steffo99/micronfig.git
synced 2024-11-21 15:44:20 +00:00
Improve documentation and tests
This commit is contained in:
parent
546c1e26aa
commit
e081fc693c
6 changed files with 315 additions and 150 deletions
|
@ -5,8 +5,10 @@
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/examples/e01_the_cave/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/examples/e01_the_cave/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/examples/e02_quick_math/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/examples/e01_the_cave/target" />
|
<excludeFolder url="file://$MODULE_DIR$/examples/e01_the_cave/target" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/examples/e02_quick_math/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
# micronfig
|
# micronfig
|
||||||
|
|
||||||
Tiny Rust crate for twelve-factor app configuration
|
Tiny Rust crate for twelve-factor app configuration
|
||||||
|
|
256
src/any.rs
Normal file
256
src/any.rs
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
//! Module defining the general [`get`] low-level function and its associated [`Source`] type.
|
||||||
|
|
||||||
|
use crate::var;
|
||||||
|
use crate::file;
|
||||||
|
|
||||||
|
|
||||||
|
/// Get a configuration value, maintaining information about how the value was retrieved.
|
||||||
|
///
|
||||||
|
/// This function tries to get a configuration value:
|
||||||
|
///
|
||||||
|
/// 1. with [`var::get`] using `name_var`, returning a [`Source::Var`]
|
||||||
|
/// 2. with [`file::get`] using `name_file`, returning a [`Source::File`]
|
||||||
|
///
|
||||||
|
/// If none of these options successfully resulted in the successful retrieval of the configuration value, [`Source::NotFound`] is returned.
|
||||||
|
///
|
||||||
|
/// All errors are bubbled up, except the ones surfacing because of the total absence of a configuration value, currently:
|
||||||
|
/// - [`var::Error::CannotReadEnvVar`]
|
||||||
|
/// - [`file::Error::CannotReadEnvVar`]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use micronfig::any::get;
|
||||||
|
/// use micronfig::any::Source;
|
||||||
|
///
|
||||||
|
/// // The NUMBER envvar has been previously set to "1".
|
||||||
|
/// # std::env::set_var("NUMBER", "1");
|
||||||
|
/// # std::env::remove_var("NUMBER_FILE");
|
||||||
|
///
|
||||||
|
/// let value = get::<&str, &str, u32>("NUMBER", "NUMBER_FILE");
|
||||||
|
/// if let Source::Var(Ok(1)) = value {} else { panic!() }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use micronfig::any::get;
|
||||||
|
/// use micronfig::any::Source;
|
||||||
|
///
|
||||||
|
/// // The NUMBER and NUMBER_FILE envvars have not been set.
|
||||||
|
/// # std::env::remove_var("NUMBER");
|
||||||
|
/// # std::env::remove_var("NUMBER_FILE");
|
||||||
|
///
|
||||||
|
/// let value = get::<&str, &str, u32>("NUMBER", "NUMBER_FILE");
|
||||||
|
/// if let Source::NotFound = value {} else { panic!() }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn get<KeyVar, KeyFile, Type>(name_var: KeyVar, name_file: KeyFile) -> Source<Type>
|
||||||
|
where KeyVar: AsRef<std::ffi::OsStr>,
|
||||||
|
KeyFile: AsRef<std::ffi::OsStr>,
|
||||||
|
Type: std::str::FromStr,
|
||||||
|
{
|
||||||
|
let v = var::get(name_var);
|
||||||
|
|
||||||
|
match v {
|
||||||
|
Err(var::Error::CannotReadEnvVar(_)) => {},
|
||||||
|
_ => return Source::Var(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = file::get(name_file);
|
||||||
|
|
||||||
|
match v {
|
||||||
|
Err(file::Error::CannotReadEnvVar(_)) => {},
|
||||||
|
_ => return Source::File(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
Source::NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// The way the result returned by [`get`] was obtained.
|
||||||
|
///
|
||||||
|
/// Since more sources might be added in the future, this function is `non_exaustive`.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Source<Type>
|
||||||
|
where Type: std::str::FromStr,
|
||||||
|
{
|
||||||
|
/// The result was not obtained, since the configuration value was not defined anywhere.
|
||||||
|
NotFound,
|
||||||
|
|
||||||
|
/// The result was obtained by [`var::get`].
|
||||||
|
Var(var::Result<Type>),
|
||||||
|
|
||||||
|
/// The result was obtained by [`file::get`].
|
||||||
|
File(file::Result<Type>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Type> Source<Type>
|
||||||
|
where Type: std::str::FromStr,
|
||||||
|
{
|
||||||
|
/// Returns any contained [`Ok`] value, consuming both `self` and the [`Source`] inside.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function panics if `self` is a [`Source::NotFound`], or if the contained value is a [`Err`].
|
||||||
|
///
|
||||||
|
/// The panic message is the `msg` given.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// Similar to [`Result::expect`].
|
||||||
|
///
|
||||||
|
/// Used by [`Self::unwrap`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use micronfig::any::Source;
|
||||||
|
///
|
||||||
|
/// let value = Source::<u8>::File(Ok(1)).expect("value to be present");
|
||||||
|
/// assert_eq!(value, 1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// use micronfig::any::Source;
|
||||||
|
/// use micronfig::file::Error as FileError;
|
||||||
|
///
|
||||||
|
/// let value = Source::<u8>::File(Err(FileError::CannotReadEnvVar(std::env::VarError::NotPresent))).expect("value to be present");
|
||||||
|
/// // Panic!
|
||||||
|
/// ```
|
||||||
|
pub fn expect(self, msg: &str) -> Type {
|
||||||
|
match self {
|
||||||
|
Self::Var(Ok(v)) => v,
|
||||||
|
Self::File(Ok(v)) => v,
|
||||||
|
_ => panic!("{}", msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns any contained [`Ok`] value, consuming both `self` and the [`Source`] inside.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function panics if `self` is a [`Source::NotFound`], or if the contained value is a [`Err`].
|
||||||
|
///
|
||||||
|
/// The panic message is the `msg` given.
|
||||||
|
///
|
||||||
|
/// # See also
|
||||||
|
///
|
||||||
|
/// Similar to [`Result::unwrap`].
|
||||||
|
///
|
||||||
|
/// Internally, it uses [`Self::expect`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use micronfig::any::Source;
|
||||||
|
///
|
||||||
|
/// let value = Source::<u8>::File(Ok(1)).unwrap();
|
||||||
|
/// assert_eq!(value, 1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// use micronfig::any::Source;
|
||||||
|
/// use micronfig::file::Error as FileError;
|
||||||
|
///
|
||||||
|
/// let value = Source::<u8>::File(Err(FileError::CannotReadEnvVar(std::env::VarError::NotPresent))).unwrap();
|
||||||
|
/// // Panic!
|
||||||
|
/// ```
|
||||||
|
pub fn unwrap(self) -> Type
|
||||||
|
{
|
||||||
|
self.expect("called `Source::unwrap()` on an invalid variant, such as `NotFound` or `_(Err(_))`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::tests::tempfile_fixture;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works_var() {
|
||||||
|
std::env::set_var("NUMBER", "1");
|
||||||
|
std::env::remove_var("NUMBER_FILE");
|
||||||
|
|
||||||
|
match get::<&str, &str, u32>("NUMBER", "NUMBER_FILE") {
|
||||||
|
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::<&str, &str, u32>("NUMBER", "NUMBER_FILE");
|
||||||
|
match n {
|
||||||
|
Source::File(Ok(1u32)) => {},
|
||||||
|
_ => panic!("expected Source::File(Ok(1u32))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_envvar() {
|
||||||
|
match get::<&str, &str, String>("MISSING_ENVVAR", "MISSING_ENVVAR_FILE") {
|
||||||
|
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::<&str, &str, u32>("NUMBER", "NUMBER_FILE") {
|
||||||
|
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::<&str, &str, u32>("NUMBER", "NUMBER_FILE") {
|
||||||
|
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::<&str, &str, u32>("NUMBER", "NUMBER_FILE") {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
18
src/file.rs
18
src/file.rs
|
@ -1,7 +1,7 @@
|
||||||
//! Module defining a function retrieving a configuration value from a file at a path specified in the environment.
|
//! Module defining the [`get`] low-level function for environment files, and its associated types.
|
||||||
|
|
||||||
|
|
||||||
/// Get a value of the requested type from the file at the path contained in the environment variable with the given name.
|
/// Get a configuration value from the file at the path contained in the environment variable with the given `name`, and convert it to the desired `Type`.
|
||||||
pub fn get<Key, Type>(name: Key) -> Result<Type>
|
pub fn get<Key, Type>(name: Key) -> Result<Type>
|
||||||
where Key: AsRef<std::ffi::OsStr>,
|
where Key: AsRef<std::ffi::OsStr>,
|
||||||
Type: std::str::FromStr,
|
Type: std::str::FromStr,
|
||||||
|
@ -25,29 +25,39 @@ pub fn get<Key, Type>(name: Key) -> Result<Type>
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A possible error encountered by [`get`].
|
/// A possible error encountered by [`get`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<ConversionError>
|
pub enum Error<ConversionError>
|
||||||
{
|
{
|
||||||
/// The environment variable could not be read.
|
/// The environment variable could not be read.
|
||||||
|
///
|
||||||
|
/// Encountered when the call to [`std::env::var`] fails.
|
||||||
CannotReadEnvVar(std::env::VarError),
|
CannotReadEnvVar(std::env::VarError),
|
||||||
|
|
||||||
/// The specified file could not be opened. (Probably it doesn't exist.)
|
/// The specified file could not be opened. (Probably it doesn't exist.)
|
||||||
|
///
|
||||||
|
/// Encountered when the call to [`std::fs::File::open`] fails.
|
||||||
CannotOpenFile(std::io::Error),
|
CannotOpenFile(std::io::Error),
|
||||||
|
|
||||||
/// The specified file could not be read.
|
/// The specified file could not be read.
|
||||||
|
///
|
||||||
|
/// Encountered when the call to [`std::io::Read::read_to_string`] fails.
|
||||||
CannotReadFile(std::io::Error),
|
CannotReadFile(std::io::Error),
|
||||||
|
|
||||||
/// The value could not be converted to the desired type.
|
/// The value could not be converted to the desired type.
|
||||||
|
///
|
||||||
|
/// Encountered when the call to [`FromStr::from_str`] fails.
|
||||||
CannotConvertValue(ConversionError),
|
CannotConvertValue(ConversionError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A possible error encountered by [`get`].
|
/// A possible error encountered by [`get`].
|
||||||
pub type Result<Type> = std::result::Result<Type, Error<<Type as std::str::FromStr>::Err>>;
|
pub type Result<Type> = std::result::Result<Type, Error<<Type as std::str::FromStr>::Err>>;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::tests::tempfile_fixture;
|
use crate::tests::tempfile_fixture;
|
||||||
|
|
||||||
|
@ -62,6 +72,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_envvar() {
|
fn missing_envvar() {
|
||||||
|
std::env::remove_var("THIS_ENVVAR_DOES_NOT_EXIST_FILE");
|
||||||
|
|
||||||
match get::<&str, String>("THIS_ENVVAR_DOES_NOT_EXIST_FILE") {
|
match get::<&str, String>("THIS_ENVVAR_DOES_NOT_EXIST_FILE") {
|
||||||
Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent)) => {},
|
Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent)) => {},
|
||||||
_ => panic!("expected Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent))"),
|
_ => panic!("expected Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent))"),
|
||||||
|
|
176
src/lib.rs
176
src/lib.rs
|
@ -1,67 +1,43 @@
|
||||||
//! A tiny crate for [twelve-factor app configuration](https://12factor.net/config).
|
//! A tiny crate for [twelve-factor app configuration](https://12factor.net/config).
|
||||||
|
//!
|
||||||
|
//! # Goals
|
||||||
|
//!
|
||||||
|
//! This crate aims to simplify developing and deploying Docker-compatible services in Rust.
|
||||||
|
//!
|
||||||
|
//! # Features
|
||||||
|
//!
|
||||||
|
//! This crate handles:
|
||||||
|
//!
|
||||||
|
//! 1. Retrieval of configuration values from multiple sources ([`any::get`])
|
||||||
|
//! 1. The environment ([`var::get`])
|
||||||
|
//! 2. Files specified in the environment ([`file::get`])
|
||||||
|
//! 2. Conversion to a value of an arbitrary type ([`std::str::FromStr`])
|
||||||
|
//! 3. Displaying a operator-friendly error in case
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! The following example:
|
||||||
|
//!
|
||||||
|
//! 1. Tries to retrieve the value of the configuration value `THIS_ENVVAR_CONTAINS_ONE`
|
||||||
|
//! 1. From the `THIS_ENVVAR_CONTAINS_ONE` environment variable
|
||||||
|
//! 2. From the contents of the file specified in the `THIS_ENVVAR_CONTAINS_ONE_FILE` environment variable
|
||||||
|
//! 2. It converts the value to a [`u8`]
|
||||||
|
//! 3. Panics with a operator-friendly error if any of these steps failed
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! todo!()
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
pub mod any;
|
||||||
pub mod var;
|
pub mod var;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
|
||||||
|
|
||||||
/// Get a value of the requested type, trying the following sources in order:
|
|
||||||
///
|
|
||||||
/// 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),
|
|
||||||
}
|
|
||||||
|
|
||||||
let v = file::get(format!("{name}_FILE"));
|
|
||||||
|
|
||||||
match v {
|
|
||||||
Err(file::Error::CannotReadEnvVar(_)) => {},
|
|
||||||
_ => return Source::File(v),
|
|
||||||
}
|
|
||||||
|
|
||||||
Source::NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Source<Type>
|
|
||||||
where Type: std::str::FromStr,
|
|
||||||
{
|
|
||||||
Var(var::Result<Type>),
|
|
||||||
File(file::Result<Type>),
|
|
||||||
NotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
/// Create a temporary file and write `content` inside it.
|
||||||
|
///
|
||||||
|
/// The file will be deleted as soon as the [`tempfile::TempPath`] is dropped.
|
||||||
pub(crate) fn tempfile_fixture(content: &str) -> tempfile::TempPath {
|
pub(crate) fn tempfile_fixture(content: &str) -> tempfile::TempPath {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
@ -72,92 +48,4 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
file.into_temp_path()
|
file.into_temp_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works_var() {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
12
src/var.rs
12
src/var.rs
|
@ -1,7 +1,7 @@
|
||||||
//! Module defining a function retrieving a configuration value from the environment.
|
//! Module defining the [`get`] low-level function for environment variables, and its associated types.
|
||||||
|
|
||||||
|
|
||||||
/// Get a value of the requested type from the environment variable with the given name.
|
/// Get a configuration value from the environment variable with the given `name`, and convert it to the desired `Type`.
|
||||||
pub fn get<Key, Type>(name: Key) -> Result<Type>
|
pub fn get<Key, Type>(name: Key) -> Result<Type>
|
||||||
where Key: AsRef<std::ffi::OsStr>,
|
where Key: AsRef<std::ffi::OsStr>,
|
||||||
Type: std::str::FromStr,
|
Type: std::str::FromStr,
|
||||||
|
@ -20,9 +20,13 @@ pub fn get<Key, Type>(name: Key) -> Result<Type>
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<ConversionError> {
|
pub enum Error<ConversionError> {
|
||||||
/// The environment variable could not be read.
|
/// The environment variable could not be read.
|
||||||
|
///
|
||||||
|
/// Encountered when the call to [`std::env::var`] fails.
|
||||||
CannotReadEnvVar(std::env::VarError),
|
CannotReadEnvVar(std::env::VarError),
|
||||||
|
|
||||||
/// The value could not be converted to the desired type.
|
/// The value could not be converted to the desired type.
|
||||||
|
///
|
||||||
|
/// Encountered when the call to [`FromStr::from_str`] fails.
|
||||||
CannotConvertValue(ConversionError),
|
CannotConvertValue(ConversionError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +36,7 @@ pub type Result<Type> = std::result::Result<Type, Error<<Type as std::str::FromS
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -45,6 +49,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_envvar() {
|
fn missing_envvar() {
|
||||||
|
std::env::remove_var("THIS_ENVVAR_DOES_NOT_EXIST");
|
||||||
|
|
||||||
match get::<&str, String>("THIS_ENVVAR_DOES_NOT_EXIST") {
|
match get::<&str, String>("THIS_ENVVAR_DOES_NOT_EXIST") {
|
||||||
Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent)) => {},
|
Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent)) => {},
|
||||||
_ => panic!("expected Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent))"),
|
_ => panic!("expected Err(Error::CannotReadEnvVar(std::env::VarError::NotPresent))"),
|
||||||
|
|
Loading…
Reference in a new issue