From 6c955c508fabda7adfd421ed86e56a7a7b5fde2f Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Fri, 28 Apr 2023 17:51:44 +0200 Subject: [PATCH] Document and refine the public API --- src/any.rs | 47 ++++++++++------- src/file.rs | 4 +- src/lib.rs | 143 +++++++++++++++++++++++++++++++++++++++++++++------- src/var.rs | 7 ++- 4 files changed, 161 insertions(+), 40 deletions(-) diff --git a/src/any.rs b/src/any.rs index cb4113b..96d1978 100644 --- a/src/any.rs +++ b/src/any.rs @@ -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::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: /// -/// 1. with [`var::get`] using `name_var`, returning a [`Source::Var`] -/// 2. with [`file::get`] using `name_file`, returning a [`Source::File`] +/// 1. with [`var::get`] using `name`, returning a [`Source::Var`] +/// 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`] /// - [`file::Error::CannotReadEnvVar`] /// @@ -27,7 +34,7 @@ use crate::file; /// # std::env::set_var("NUMBER", "1"); /// # 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!() } /// ``` /// @@ -39,22 +46,26 @@ use crate::file; /// # std::env::remove_var("NUMBER"); /// # 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!() } /// ``` /// -pub fn get(name_var: KeyVar, name_file: KeyFile) -> Source +pub fn get(name: KeyVar, file_suffix: KeyFile) -> Source where KeyVar: AsRef, KeyFile: AsRef, Type: std::str::FromStr, + ::Err: std::fmt::Debug, { - let v = var::get(name_var); + let v = var::get(&name); match v { Err(var::Error::CannotReadEnvVar(_)) => {}, _ => 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); match v { @@ -72,6 +83,7 @@ pub fn get(name_var: KeyVar, name_file: KeyFile) -> Sourc #[non_exhaustive] pub enum Source where Type: std::str::FromStr, + ::Err: std::fmt::Debug, { /// The result was not obtained, since the configuration value was not defined anywhere. NotFound, @@ -85,6 +97,7 @@ pub enum Source impl Source where Type: std::str::FromStr, + ::Err: std::fmt::Debug, { /// Returns any contained [`Ok`] value, consuming both `self` and the [`Source`] inside. /// @@ -130,8 +143,6 @@ impl Source /// /// 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`]. @@ -171,7 +182,7 @@ pub(crate) mod tests { std::env::set_var("NUMBER", "1"); 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)) => {}, _ => panic!("expected Source::Var(Ok(1u32))") } @@ -183,7 +194,7 @@ pub(crate) mod tests { std::env::remove_var("NUMBER"); 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 { Source::File(Ok(1u32)) => {}, _ => panic!("expected Source::File(Ok(1u32))") @@ -192,7 +203,7 @@ pub(crate) mod tests { #[test] fn missing_envvar() { - match get::<&str, &str, String>("MISSING_ENVVAR", "MISSING_ENVVAR_FILE") { + match get::<&str, &str, String>("MISSING_ENVVAR", "_FILE") { Source::NotFound => {}, _ => panic!("expected Source::NotFound"), } @@ -203,7 +214,7 @@ pub(crate) mod tests { std::env::remove_var("NUMBER"); 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(_))) => {}, _ => panic!("expected Source::File(Err(file::Error::CannotOpenFile(_)))"), } @@ -214,7 +225,7 @@ pub(crate) mod tests { std::env::set_var("NUMBER", "XYZ"); 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(_))) => {}, _ => 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::remove_var("NUMBER"); - match get::<&str, &str, u32>("NUMBER", "NUMBER_FILE") { + match get::<&str, &str, u32>("NUMBER", "_FILE") { Source::File(Err(file::Error::CannotConvertValue(_))) => {}, _ => panic!("expected Source::File(Err(file::Error::CannotConvertValue(_)))"), } diff --git a/src/file.rs b/src/file.rs index 4df3dcb..b605baa 100644 --- a/src/file.rs +++ b/src/file.rs @@ -5,6 +5,7 @@ pub fn get(name: Key) -> Result where Key: AsRef, Type: std::str::FromStr, + ::Err: std::fmt::Debug, { let path = std::env::var(name) .map_err(Error::CannotReadEnvVar)?; @@ -27,8 +28,9 @@ pub fn get(name: Key) -> Result /// A possible error encountered by [`get`]. -#[derive(Debug)] +#[derive(std::fmt::Debug)] pub enum Error + where ConversionError: std::fmt::Debug, { /// The environment variable could not be read. /// diff --git a/src/lib.rs b/src/lib.rs index dd500e7..ebfe90f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,30 +8,135 @@ //! //! 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!() -//! ``` +//! 1. Retrieval of configuration values from multiple sources +//! 1. The environment +//! 2. Files specified in the environment +//! 2. Conversion to a value of an arbitrary type +//! 3. Displaying a operator-friendly error if case one of this steps did not succeed pub mod any; pub mod var; 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(name: &str) -> Type + where Type: 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::()), + 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::()), + 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 = 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 = 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(name: &str) -> Option + where Type: 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::()), + 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::()), + 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)] pub(crate) mod tests { diff --git a/src/var.rs b/src/var.rs index c131623..3b4e090 100644 --- a/src/var.rs +++ b/src/var.rs @@ -5,6 +5,7 @@ pub fn get(name: Key) -> Result where Key: AsRef, Type: std::str::FromStr, + ::Err: std::fmt::Debug, { let data = std::env::var(name) .map_err(Error::CannotReadEnvVar)?; @@ -17,8 +18,10 @@ pub fn get(name: Key) -> Result /// A possible error encountered by [`get`]. -#[derive(Debug)] -pub enum Error { +#[derive(std::fmt::Debug)] +pub enum Error + where ConversionError: std::fmt::Debug, +{ /// The environment variable could not be read. /// /// Encountered when the call to [`std::env::var`] fails.