1
Fork 0
mirror of https://github.com/Steffo99/micronfig.git synced 2024-10-16 14:37:29 +00:00

Document and refine the public API

This commit is contained in:
Steffo 2023-04-28 17:51:44 +02:00
parent e081fc693c
commit 6c955c508f
Signed by: steffo
GPG key ID: 2A24051445686895
4 changed files with 161 additions and 40 deletions

View file

@ -1,19 +1,26 @@
//! Module defining the general [`get`] low-level function and its associated [`Source`] type. //! Module defining the general [`value`] high-level function, the general [`get`] low-level function, and its associated [`Source`] type.
use std::ffi::OsString;
use crate::var; use crate::var;
use crate::file; use crate::file;
/// Get a configuration value, maintaining information about how the value was retrieved. /// Get a configuration value from the first available source and convert it to the given `Type`, additionally returning information about how the value was retrieved.
///
/// # Process
/// ///
/// This function tries to get a configuration value: /// This function tries to get a configuration value:
/// ///
/// 1. with [`var::get`] using `name_var`, returning a [`Source::Var`] /// 1. with [`var::get`] using `name`, returning a [`Source::Var`]
/// 2. with [`file::get`] using `name_file`, returning a [`Source::File`] /// 2. with [`file::get`] using `name + file_suffix`, returning a [`Source::File`]
/// ///
/// If none of these options successfully resulted in the successful retrieval of the configuration value, [`Source::NotFound`] is returned. /// If none of these options successfully resulted in the successful retrieval of the configuration value, [`Source::NotFound`] is returned instead.
/// ///
/// All errors are bubbled up, except the ones surfacing because of the total absence of a configuration value, currently: /// # Errors
///
/// All errors are bubbled up, except the ones surfacing because of the total absence of a configuration value, which make the function try the next available source.
///
/// Currently, those are:
/// - [`var::Error::CannotReadEnvVar`] /// - [`var::Error::CannotReadEnvVar`]
/// - [`file::Error::CannotReadEnvVar`] /// - [`file::Error::CannotReadEnvVar`]
/// ///
@ -27,7 +34,7 @@ use crate::file;
/// # std::env::set_var("NUMBER", "1"); /// # std::env::set_var("NUMBER", "1");
/// # std::env::remove_var("NUMBER_FILE"); /// # std::env::remove_var("NUMBER_FILE");
/// ///
/// let value = get::<&str, &str, u32>("NUMBER", "NUMBER_FILE"); /// let value = get::<&str, &str, u32>("NUMBER", "_FILE");
/// if let Source::Var(Ok(1)) = value {} else { panic!() } /// if let Source::Var(Ok(1)) = value {} else { panic!() }
/// ``` /// ```
/// ///
@ -39,22 +46,26 @@ use crate::file;
/// # std::env::remove_var("NUMBER"); /// # std::env::remove_var("NUMBER");
/// # std::env::remove_var("NUMBER_FILE"); /// # std::env::remove_var("NUMBER_FILE");
/// ///
/// let value = get::<&str, &str, u32>("NUMBER", "NUMBER_FILE"); /// let value = get::<&str, &str, u32>("NUMBER", "_FILE");
/// if let Source::NotFound = value {} else { panic!() } /// if let Source::NotFound = value {} else { panic!() }
/// ``` /// ```
/// ///
pub fn get<KeyVar, KeyFile, Type>(name_var: KeyVar, name_file: KeyFile) -> Source<Type> pub fn get<KeyVar, KeyFile, Type>(name: KeyVar, file_suffix: KeyFile) -> Source<Type>
where KeyVar: AsRef<std::ffi::OsStr>, where KeyVar: AsRef<std::ffi::OsStr>,
KeyFile: AsRef<std::ffi::OsStr>, KeyFile: AsRef<std::ffi::OsStr>,
Type: std::str::FromStr, Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{ {
let v = var::get(name_var); let v = var::get(&name);
match v { match v {
Err(var::Error::CannotReadEnvVar(_)) => {}, Err(var::Error::CannotReadEnvVar(_)) => {},
_ => return Source::Var(v), _ => return Source::Var(v),
} }
let mut name_file = OsString::new();
name_file.push(name);
name_file.push(file_suffix);
let v = file::get(name_file); let v = file::get(name_file);
match v { match v {
@ -72,6 +83,7 @@ pub fn get<KeyVar, KeyFile, Type>(name_var: KeyVar, name_file: KeyFile) -> Sourc
#[non_exhaustive] #[non_exhaustive]
pub enum Source<Type> pub enum Source<Type>
where Type: std::str::FromStr, where Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{ {
/// The result was not obtained, since the configuration value was not defined anywhere. /// The result was not obtained, since the configuration value was not defined anywhere.
NotFound, NotFound,
@ -85,6 +97,7 @@ pub enum Source<Type>
impl<Type> Source<Type> impl<Type> Source<Type>
where Type: std::str::FromStr, where Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{ {
/// Returns any contained [`Ok`] value, consuming both `self` and the [`Source`] inside. /// Returns any contained [`Ok`] value, consuming both `self` and the [`Source`] inside.
/// ///
@ -130,8 +143,6 @@ impl<Type> Source<Type>
/// ///
/// This function panics if `self` is a [`Source::NotFound`], or if the contained value is a [`Err`]. /// 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 /// # See also
/// ///
/// Similar to [`Result::unwrap`]. /// Similar to [`Result::unwrap`].
@ -171,7 +182,7 @@ pub(crate) mod tests {
std::env::set_var("NUMBER", "1"); std::env::set_var("NUMBER", "1");
std::env::remove_var("NUMBER_FILE"); std::env::remove_var("NUMBER_FILE");
match get::<&str, &str, u32>("NUMBER", "NUMBER_FILE") { match get::<&str, &str, u32>("NUMBER", "_FILE") {
Source::Var(Ok(1u32)) => {}, Source::Var(Ok(1u32)) => {},
_ => panic!("expected Source::Var(Ok(1u32))") _ => panic!("expected Source::Var(Ok(1u32))")
} }
@ -183,7 +194,7 @@ pub(crate) mod tests {
std::env::remove_var("NUMBER"); std::env::remove_var("NUMBER");
std::env::set_var("NUMBER_FILE", file.as_os_str()); std::env::set_var("NUMBER_FILE", file.as_os_str());
let n = get::<&str, &str, u32>("NUMBER", "NUMBER_FILE"); let n = get::<&str, &str, u32>("NUMBER", "_FILE");
match n { match n {
Source::File(Ok(1u32)) => {}, Source::File(Ok(1u32)) => {},
_ => panic!("expected Source::File(Ok(1u32))") _ => panic!("expected Source::File(Ok(1u32))")
@ -192,7 +203,7 @@ pub(crate) mod tests {
#[test] #[test]
fn missing_envvar() { fn missing_envvar() {
match get::<&str, &str, String>("MISSING_ENVVAR", "MISSING_ENVVAR_FILE") { match get::<&str, &str, String>("MISSING_ENVVAR", "_FILE") {
Source::NotFound => {}, Source::NotFound => {},
_ => panic!("expected Source::NotFound"), _ => panic!("expected Source::NotFound"),
} }
@ -203,7 +214,7 @@ pub(crate) mod tests {
std::env::remove_var("NUMBER"); std::env::remove_var("NUMBER");
std::env::set_var("NUMBER_FILE", "/this/file/does/not/exist"); std::env::set_var("NUMBER_FILE", "/this/file/does/not/exist");
match get::<&str, &str, u32>("NUMBER", "NUMBER_FILE") { match get::<&str, &str, u32>("NUMBER", "_FILE") {
Source::File(Err(file::Error::CannotOpenFile(_))) => {}, Source::File(Err(file::Error::CannotOpenFile(_))) => {},
_ => panic!("expected Source::File(Err(file::Error::CannotOpenFile(_)))"), _ => panic!("expected Source::File(Err(file::Error::CannotOpenFile(_)))"),
} }
@ -214,7 +225,7 @@ pub(crate) mod tests {
std::env::set_var("NUMBER", "XYZ"); std::env::set_var("NUMBER", "XYZ");
std::env::remove_var("NUMBER_FILE"); std::env::remove_var("NUMBER_FILE");
match get::<&str, &str, u32>("NUMBER", "NUMBER_FILE") { match get::<&str, &str, u32>("NUMBER", "_FILE") {
Source::Var(Err(var::Error::CannotConvertValue(_))) => {}, Source::Var(Err(var::Error::CannotConvertValue(_))) => {},
_ => panic!("expected Source::Var(Err(var::Error::CannotConvertValue(_)))"), _ => panic!("expected Source::Var(Err(var::Error::CannotConvertValue(_)))"),
} }
@ -226,7 +237,7 @@ pub(crate) mod tests {
std::env::set_var("NUMBER_FILE", file.as_os_str()); std::env::set_var("NUMBER_FILE", file.as_os_str());
std::env::remove_var("NUMBER"); std::env::remove_var("NUMBER");
match get::<&str, &str, u32>("NUMBER", "NUMBER_FILE") { match get::<&str, &str, u32>("NUMBER", "_FILE") {
Source::File(Err(file::Error::CannotConvertValue(_))) => {}, Source::File(Err(file::Error::CannotConvertValue(_))) => {},
_ => panic!("expected Source::File(Err(file::Error::CannotConvertValue(_)))"), _ => panic!("expected Source::File(Err(file::Error::CannotConvertValue(_)))"),
} }

View file

@ -5,6 +5,7 @@
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,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{ {
let path = std::env::var(name) let path = std::env::var(name)
.map_err(Error::CannotReadEnvVar)?; .map_err(Error::CannotReadEnvVar)?;
@ -27,8 +28,9 @@ pub fn get<Key, Type>(name: Key) -> Result<Type>
/// A possible error encountered by [`get`]. /// A possible error encountered by [`get`].
#[derive(Debug)] #[derive(std::fmt::Debug)]
pub enum Error<ConversionError> pub enum Error<ConversionError>
where ConversionError: std::fmt::Debug,
{ {
/// The environment variable could not be read. /// The environment variable could not be read.
/// ///

View file

@ -8,30 +8,135 @@
//! //!
//! This crate handles: //! This crate handles:
//! //!
//! 1. Retrieval of configuration values from multiple sources ([`any::get`]) //! 1. Retrieval of configuration values from multiple sources
//! 1. The environment ([`var::get`]) //! 1. The environment
//! 2. Files specified in the environment ([`file::get`]) //! 2. Files specified in the environment
//! 2. Conversion to a value of an arbitrary type ([`std::str::FromStr`]) //! 2. Conversion to a value of an arbitrary type
//! 3. Displaying a operator-friendly error in case //! 3. Displaying a operator-friendly error if case one of this steps did not succeed
//!
//! # 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 any;
pub mod var; pub mod var;
pub mod file; pub mod file;
/// Get the configuration value with the given `name` and convert it to the given `Type`.
///
/// # Panics
///
/// Any error encountered by this function causes a panic with a message describing what went wrong.
///
/// The same thing happens if the configuration value could not be retrieved by any source.
///
/// # Examples
///
/// ```
/// // The NUMBER envvar has been previously set to "1".
/// # std::env::set_var("NUMBER", "1");
/// # std::env::remove_var("NUMBER_FILE");
///
/// let value: u8 = micronfig::required("NUMBER");
/// assert_eq!(value, 1u8);
/// ```
///
/// ```should_panic
/// // The NUMBER envvar has not been set.
/// # std::env::remove_var("NUMBER");
/// # std::env::remove_var("NUMBER_FILE");
///
/// let value: u8 = micronfig::required("NUMBER");
/// // Panic: The configuration value NUMBER is not defined.
/// ```
///
/// # See also
///
/// [`any::get`], the function called by this one to get the configuration value.
///
pub fn required<Type>(name: &str) -> Type
where Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
use crate::any::{get, Source};
match get(name, "_FILE") {
Source::Var(Ok(v)) => v,
Source::Var(Err(var::Error::CannotConvertValue(_))) =>
panic!("The contents of the {} environment variable could not be converted to a {}.", &name, &std::any::type_name::<Type>()),
Source::Var(Err(var::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::File(Ok(v)) => v,
Source::File(Err(file::Error::CannotConvertValue(_))) =>
panic!("The contents of the file at {} could not be converted to a {}.", &name, &std::any::type_name::<Type>()),
Source::File(Err(file::Error::CannotOpenFile(err))) =>
panic!("The file at {} could not be opened: {}", &name, &err),
Source::File(Err(file::Error::CannotReadFile(err))) =>
panic!("The contents of the file at {} could not be read: {}", &name, &err),
Source::File(Err(file::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::NotFound =>
panic!("The configuration value {} is not defined.", &name),
}
}
/// Get the configuration value with the given `name` and convert it to the given `Type`, if it was defined somewhere.
///
/// # Panics
///
/// Any error encountered by this function causes a panic with a message describing what went wrong.
///
/// # Examples
///
/// ```
/// // The NUMBER envvar has been previously set to "1".
/// # std::env::set_var("NUMBER", "1");
/// # std::env::remove_var("NUMBER_FILE");
///
/// let value: Option<u8> = micronfig::optional("NUMBER");
/// assert_eq!(value, Some(1u8));
/// ```
///
/// ```
/// // The NUMBER envvar has not been set.
/// # std::env::remove_var("NUMBER");
/// # std::env::remove_var("NUMBER_FILE");
///
/// let value: Option<u8> = micronfig::optional("NUMBER");
/// assert_eq!(value, None);
/// ```
///
/// # See also
///
/// [`any::get`], the function called by this one to get the configuration value.
///
pub fn optional<Type>(name: &str) -> Option<Type>
where Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
use crate::any::{get, Source};
match get(name, "_FILE") {
Source::Var(Ok(v)) => Some(v),
Source::Var(Err(var::Error::CannotConvertValue(_))) =>
panic!("The contents of the {} environment variable could not be converted to a {}.", &name, &std::any::type_name::<Type>()),
Source::Var(Err(var::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::File(Ok(v)) => Some(v),
Source::File(Err(file::Error::CannotConvertValue(_))) =>
panic!("The contents of the file at {} could not be converted to a {}.", &name, &std::any::type_name::<Type>()),
Source::File(Err(file::Error::CannotOpenFile(err))) =>
panic!("The file at {} could not be opened: {}", &name, &err),
Source::File(Err(file::Error::CannotReadFile(err))) =>
panic!("The contents of the file at {} could not be read: {}", &name, &err),
Source::File(Err(file::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::NotFound => None,
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {

View file

@ -5,6 +5,7 @@
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,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{ {
let data = std::env::var(name) let data = std::env::var(name)
.map_err(Error::CannotReadEnvVar)?; .map_err(Error::CannotReadEnvVar)?;
@ -17,8 +18,10 @@ pub fn get<Key, Type>(name: Key) -> Result<Type>
/// A possible error encountered by [`get`]. /// A possible error encountered by [`get`].
#[derive(Debug)] #[derive(std::fmt::Debug)]
pub enum Error<ConversionError> { pub enum Error<ConversionError>
where ConversionError: std::fmt::Debug,
{
/// The environment variable could not be read. /// The environment variable could not be read.
/// ///
/// Encountered when the call to [`std::env::var`] fails. /// Encountered when the call to [`std::env::var`] fails.