1
Fork 0
mirror of https://github.com/Steffo99/micronfig.git synced 2024-11-25 17:44:18 +00:00

Clear the crate and start over

This commit is contained in:
Steffo 2023-12-29 10:14:52 +01:00
parent 825a20b19a
commit c6df6cb3af
Signed by: steffo
GPG key ID: 2A24051445686895
16 changed files with 0 additions and 1070 deletions

View file

@ -1,8 +0,0 @@
[package]
name = "e01_the_cave"
description = "Echoes back the value of ECHO."
version = "0.0.0"
edition = "2021"
[dependencies]
micronfig = { path = "../.." }

View file

@ -1,9 +0,0 @@
use std::fmt::Display;
micronfig::required!(ECHO, String);
fn main() {
println!("{}", *ECHO);
}

View file

@ -1,8 +0,0 @@
[package]
name = "e02_quick_math"
description = "Performs OPERATOR between FIRST and SECOND."
version = "0.0.0"
edition = "2021"
[dependencies]
micronfig = { path = "../.." }

View file

@ -1,52 +0,0 @@
use std::fmt::{Display, Formatter};
use std::str::FromStr;
micronfig::required!(FIRST, u64);
micronfig::required!(SECOND, u64);
micronfig::required!(OPERATOR, Operator);
fn main() {
let result = match *OPERATOR {
Operator::Sum => (*FIRST) + (*SECOND),
Operator::Subtraction => (*FIRST) - (*SECOND),
Operator::Multiplication => (*FIRST) * (*SECOND),
Operator::Division => (*FIRST) / (*SECOND),
};
println!("{} {} {} = {}", *FIRST, *OPERATOR, *SECOND, result)
}
pub enum Operator {
Sum,
Subtraction,
Multiplication,
Division,
}
impl FromStr for Operator {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"+" => Ok(Self::Sum),
"-" => Ok(Self::Subtraction),
"*" => Ok(Self::Multiplication),
"/" => Ok(Self::Division),
_ => Err(())
}
}
}
impl Display for Operator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
Self::Sum => "+",
Self::Subtraction => "-",
Self::Multiplication => "*",
Self::Division => "/",
})
}
}

View file

@ -1,8 +0,0 @@
[package]
name = "e03_order_a_pizza"
description = "Order a pizza using micronfig!"
version = "0.0.0"
edition = "2021"
[dependencies]
micronfig = { path = "../.." }

View file

@ -1,119 +0,0 @@
use std::fmt::Formatter;
use std::net::IpAddr;
use std::str::FromStr;
// The name of the person who ordered the pizza.
micronfig::required!(FULLNAME, String);
// The (IP) address the pizza should be delivered to.
micronfig::required!(DESTINATION, IpAddr);
// The base of the pizza to add toppings on.
micronfig::required!(PIZZABASE, PizzaBase);
// The toppings to add to the pizza.
micronfig::optional!(PIZZATOPPINGS, PizzaToppingsList);
// A pizza with no toppings, to use as fallback.
const PIZZATOPPINGS_NONE: PizzaToppingsList = PizzaToppingsList{ list: vec![] };
fn main() {
// Let's print the order!
println!("Pizza Order");
println!("===========");
println!();
println!("Base:");
println!("- {}", *PIZZABASE);
println!();
println!("Toppings:");
for topping in &(*PIZZATOPPINGS).as_ref().unwrap_or(&PIZZATOPPINGS_NONE).list {
println!("- {}", &topping);
};
println!();
println!("Deliver to:");
println!("{} @ {}", *FULLNAME, *DESTINATION)
}
/// A possible base of pizza.
#[derive(Clone, Copy, Debug)]
enum PizzaBase {
/// Just the pizza dough, with nothing else on top f it.
Blank,
/// Pizza dough with tomato on top.
Red,
/// Pizza dough with mozzarella on top.
White,
/// Pizza dough with both tomato and mozzarella on top.
Margherita,
}
impl FromStr for PizzaBase {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
// Italian
"vuota" => Ok(Self::Blank),
"stria" => Ok(Self::Blank),
"rossa" => Ok(Self::Red),
"marinara" => Ok(Self::Red),
"pomodoro" => Ok(Self::Red),
"bianca" => Ok(Self::White),
"mozzarella" => Ok(Self::White),
"regina" => Ok(Self::Margherita),
"margherita" => Ok(Self::Margherita),
"normale" => Ok(Self::Margherita),
"entrambi" => Ok(Self::Margherita),
// English
"blank" => Ok(Self::Blank),
"red" => Ok(Self::Red),
"tomato" => Ok(Self::Red),
"white" => Ok(Self::White),
"cheese" => Ok(Self::White),
"both" => Ok(Self::Margherita),
"normal" => Ok(Self::Margherita),
// Unknown
_ => Err("Unknown pizza base; ensure you have written the name in either English or Italian!"),
}
}
}
impl std::fmt::Display for PizzaBase {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
PizzaBase::Blank => "Blank (Empty)",
PizzaBase::Red => "Red (Tomato)",
PizzaBase::White => "White (Mozzarella)",
PizzaBase::Margherita => "Margherita (Tomato + Mozzarella)"
})
}
}
/// The toppings
#[derive(Clone, Debug)]
struct PizzaToppingsList {
pub list: Vec<String>
}
impl FromStr for PizzaToppingsList {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let list: Vec<String> = s.split(",").map(|s| s.to_string()).collect();
for topping in list.iter() {
// Ensure compatibility with https://github.com/rust-lang/rust/pull/70645
if ["pineapple", "ananas"].contains(&topping.as_str()) {
return Err("Ruining pizzas is not allowed by the Rust compiler.")
}
}
Ok(
PizzaToppingsList {
list
}
)
}
}

View file

@ -1,153 +0,0 @@
//! High-level API — Handle errors automatically.
//!
//! It can be useful if you want to specify when configuration values are loaded in the lifecycle of your binary.
/// Get a value from the first available source, panicking with a human-readable message in case the value is missing or cannot be processed.
///
/// # Process
///
/// This function:
///
/// 1. calls [`crate::multi::get`] with the given key and a file suffix of `_FILE`
/// 2. pattern matches errors and [`panic`]s if an error is caught.
///
/// # 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
///
/// Retrieve a configuration value from either the `USER` environment variable or the `USER_FILE` file, maintaining it as a [`String`]:
/// ```
/// use micronfig::handle::get_required;
/// #
/// # std::env::set_var("USER", "steffo");
/// # std::env::remove_var("USER_FILE");
///
/// let user: String = get_required("USER");
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS` environment variable or the `IP_ADDRESS_FILE` file, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// use micronfig::handle::get_required;
/// #
/// # std::env::set_var("IP_ADDRESS", "192.168.1.1");
/// # std::env::remove_var("IP_ADDRESS_FILE");
///
/// let ip_addr: IpAddr = get_required("IP_ADDRESS");
/// ```
///
/// # See also
///
/// [`get_optional`], which has the same behaviour but does not panic if the value is not found, instead returning [`None`].
///
/// # Possible future improvements
///
/// Possibly refactor this to a method of [`crate::multi::Source`].
///
pub fn get_required<Type>(key: &str) -> Type
where Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
use crate::multi::{get, Source};
use crate::single::{envvars, envfiles};
match get(key, "_FILE") {
Source::EnvVar(Ok(v)) => v,
Source::EnvVar(Err(envvars::Error::CannotConvertValue(err))) =>
panic!("The contents of the {} environment variable could not be converted to a {}: {:?}", &key, &std::any::type_name::<Type>(), &err),
Source::EnvVar(Err(envvars::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::EnvFile(Ok(v)) => v,
Source::EnvFile(Err(envfiles::Error::CannotConvertValue(err))) =>
panic!("The contents of the file at {} could not be converted to a {}: {:?}", &key, &std::any::type_name::<Type>(), &err),
Source::EnvFile(Err(envfiles::Error::CannotOpenFile(err))) =>
panic!("The file at {} could not be opened: {}", &key, &err),
Source::EnvFile(Err(envfiles::Error::CannotReadFile(err))) =>
panic!("The contents of the file at {} could not be read: {}", &key, &err),
Source::EnvFile(Err(envfiles::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::NotFound =>
panic!("The configuration value {} is not defined.", &key),
}
}
/// Try to get a value from the first available source, panicking with a human-readable message in case it cannot be processed.
///
/// # Process
///
/// This function:
///
/// 1. calls [`crate::multi::get`] with the given key and a file suffix of `_FILE`
/// 2. pattern matches errors and [`panic`]s if an error is caught.
///
/// # Panics
///
/// Any error encountered by this function causes a panic with a message describing what went wrong.
///
/// # Examples
///
/// Retrieve a configuration value from either the `USER` environment variable or the `USER_FILE` file, maintaining it as a [`String`]:
/// ```
/// use micronfig::handle::get_optional;
/// #
/// # std::env::set_var("USER", "steffo");
/// # std::env::remove_var("USER_FILE");
///
/// let user: Option<String> = get_optional("USER");
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS` environment variable or the `IP_ADDRESS_FILE` file, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// use micronfig::handle::get_optional;
/// #
/// # std::env::set_var("IP_ADDRESS", "192.168.1.1");
/// # std::env::remove_var("IP_ADDRESS_FILE");
///
/// let ip_addr: Option<IpAddr> = get_optional("IP_ADDRESS");
/// ```
///
/// # See also
///
/// [`get_required`], which has the same behaviour but does panics if the value is not found.
///
/// # Possible future improvements
///
/// Possibly refactor this to a method of [`crate::multi::Source`].
///
pub fn get_optional<Type>(name: &str) -> Option<Type>
where Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
use crate::multi::{get, Source};
use crate::single::{envvars, envfiles};
match get(name, "_FILE") {
Source::EnvVar(Ok(v)) => Some(v),
Source::EnvVar(Err(envvars::Error::CannotConvertValue(err))) =>
panic!("The contents of the {} environment variable could not be converted to a {}: {:?}", &name, &std::any::type_name::<Type>(), &err),
Source::EnvVar(Err(envvars::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::EnvFile(Ok(v)) => Some(v),
Source::EnvFile(Err(envfiles::Error::CannotConvertValue(err))) =>
panic!("The contents of the file at {} could not be converted to a {}: {:?}", &name, &std::any::type_name::<Type>(), &err),
Source::EnvFile(Err(envfiles::Error::CannotOpenFile(err))) =>
panic!("The file at {} could not be opened: {}", &name, &err),
Source::EnvFile(Err(envfiles::Error::CannotReadFile(err))) =>
panic!("The contents of the file at {} could not be read: {}", &name, &err),
Source::EnvFile(Err(envfiles::Error::CannotReadEnvVar(_))) =>
panic!("Something unexpected happened in micronfig. Please report this as a bug!"),
Source::NotFound => None,
}
}

View file

@ -1,52 +0,0 @@
//! Tiny crate for simple configuration management.
//!
//! > **Unstable**; I haven't fully committed to the API yet, so it might change wildly in the following minor versions (`0.x.0`).
//!
//! # Features
//!
//! This crate handles:
//!
//! - Retrieval of values of configuration properties from multiple sources, such as environment variables or files
//! - Parsing of retrieved data
//! - Displaying human-readable errors if case a step does not succeed
//!
//! # Usage
//!
//! This crate has four levels of abstraction, each one with a different usage method.
//!
//! In order from the highest to the lowest, they are:
//!
//! 1. **Recommended**: [`required`] and [`optional`], macros which allow you to define global, lazily-evaluated, configuration values;
//! 2. [`handle::get_required`] and [`handle::get_optional`], functions which allow you to get a configuration value in a specific moment, without having to consider handling errors;
//! 3. [`multi::get`], function which behaves in the same way as the previous two, but returns [`multi::Source`] instead, allowing you to handle errors how you prefer;
//! 4. [`single`], module containing submodules allowing the retrieval of configuration values from a single source, returning a source-specific [`Result`].
//!
//! ## Examples
//!
//! Some examples are provided in the crate source, [inside the `examples/` directory](https://github.com/Steffo99/micronfig/tree/main/examples).
#![warn(missing_docs)]
#![doc(html_logo_url = "https://raw.githubusercontent.com/Steffo99/micronfig/main/icon.png")]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod single;
#[cfg(feature = "multi")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "multi")))]
pub mod multi;
#[cfg(feature = "handle")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "handle")))]
pub mod handle;
#[cfg(feature = "macros")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
pub use lazy_static;
#[cfg(feature = "macros")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "macros")))]
pub mod macros;
#[cfg(feature = "testing")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "testing")))]
pub mod testing;

View file

@ -1,82 +0,0 @@
//! Highest-level API — Define lazy statics.
//!
//! The recommended way to use the library.
/// Define a required configuration value with a certain type.
///
/// # Process
///
/// This macro:
///
/// 1. uses [`lazy_static::lazy_static`] to define a new static variable (and associated struct)
/// 2. uses [`crate::handle::get_required`] to get the configuration value and handle eventual errors
///
/// # Examples
///
/// Define a configuration value with the `USER` key, a [`String`]:
/// ```
/// # std::env::set_var("USER", "steffo");
/// # std::env::remove_var("USER_FILE");
/// #
/// micronfig::required!(USER, String);
/// println!("{:?}", *USER);
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS` environment variable or the `IP_ADDRESS_FILE` file, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// #
/// # std::env::set_var("IP_ADDRESS", "192.168.1.1");
/// # std::env::remove_var("IP_ADDRESS_FILE");
///
/// micronfig::required!(IP_ADDRESS, IpAddr);
/// println!("{:?}", *IP_ADDRESS);
/// ```
#[macro_export]
macro_rules! required {
($identifier:ident, $kind:ty) => {
$crate::lazy_static::lazy_static! {
pub(crate) static ref $identifier: $kind = $crate::handle::get_required::<$kind>(stringify!($identifier));
}
};
}
/// Define a optional configuration value with a certain type.
///
/// # Process
///
/// This macro:
///
/// 1. uses [`lazy_static::lazy_static`] to define a new static variable (and associated struct)
/// 2. uses [`crate::handle::get_optional`] to get the configuration value and handle eventual errors
///
/// # Examples
///
/// Define a configuration value with the `USER` key, a [`String`]:
/// ```
/// # std::env::set_var("USER", "steffo");
/// # std::env::remove_var("USER_FILE");
/// #
/// micronfig::optional!(USER, String);
/// println!("{:?}", *USER);
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS` environment variable or the `IP_ADDRESS_FILE` file, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// #
/// # std::env::set_var("IP_ADDRESS", "192.168.1.1");
/// # std::env::remove_var("IP_ADDRESS_FILE");
///
/// micronfig::optional!(IP_ADDRESS, IpAddr);
/// println!("{:?}", *IP_ADDRESS);
/// ```
#[macro_export]
macro_rules! optional {
($identifier:ident, $kind:ty) => {
$crate::lazy_static::lazy_static! {
pub(crate) static ref $identifier: Option<$kind> = $crate::handle::get_optional::<$kind>(stringify!($identifier));
}
}
}

View file

@ -1,293 +0,0 @@
//! Middle-level API — Use all available configuration sources.
//!
//! It can be useful if you want more control on how errors are handled or on how the key is passed to the [`crate::single`] sources.
use std::ffi::OsString;
#[cfg(feature = "single_envvars")] use crate::single::envvars;
#[cfg(feature = "single_envfiles")] use crate::single::envfiles;
/// Get a value from the first available source, additionally returning information about how the value was retrieved.
///
/// # Process
///
/// This function tries to `get` a configuration value:
///
/// 1. with [`envvars::get`], using `key`, returning a [`Source::EnvVar`]
/// 2. with [`envfiles::get`], using `key + key_suffix_file`, returning a [`Source::EnvFile`]
///
/// If none of these options successfully resulted in the successful retrieval of the configuration value, [`Source::NotFound`] is returned instead.
///
/// # Errors
///
/// All errors encountered are bubbled up, except the ones surfacing because of the total absence of a configuration value, which make the function immediately try the next available source.
///
/// Currently, those errors are:
/// - [`envvars::Error::CannotReadEnvVar`]
/// - [`envfiles::Error::CannotReadEnvVar`]
///
/// # Examples
///
/// Retrieve a configuration value from either the `USER` environment variable or the `USER_FILE` file, maintaining it as a [`String`]:
/// ```
/// use micronfig::multi::get;
/// use micronfig::multi::Source;
/// #
/// # std::env::set_var("USER", "steffo");
/// # std::env::remove_var("USER_FILE");
///
/// let user: Source<u32> = get("USER", "_FILE");
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS` environment variable or the `IP_ADDRESS_FILE` file, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// use micronfig::multi::get;
/// use micronfig::multi::Source;
/// #
/// # std::env::set_var("IP_ADDRESS", "192.168.1.1");
/// # std::env::remove_var("IP_ADDRESS_FILE");
///
/// let ip_addr: Source<IpAddr> = get("IP_ADDRESS", "_FILE");
/// ```
///
pub fn get<Key, KeySuffixFile, Type>(key: Key, key_suffix_file: KeySuffixFile) -> Source<Type>
where Key: AsRef<std::ffi::OsStr>,
KeySuffixFile: AsRef<std::ffi::OsStr>,
Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
if cfg!(feature = "single_envvars") {
let v = envvars::get(&key);
match v {
Err(envvars::Error::CannotReadEnvVar(_)) => {},
_ => return Source::EnvVar(v),
}
}
if cfg!(feature = "single_envfiles") {
let mut key_file = OsString::new();
key_file.push(key);
key_file.push(key_suffix_file);
let v = envfiles::get(key_file);
match v {
Err(envfiles::Error::CannotReadEnvVar(_)) => {},
_ => return Source::EnvFile(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]
#[derive(Debug)]
pub enum Source<Type>
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.
NotFound,
/// The result was obtained by [`envvars::get`].
#[cfg(feature = "single_envvars")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "single_envvars")))]
EnvVar(envvars::Result<Type>),
/// The result was obtained by [`envfiles::get`].
#[cfg(feature = "single_envfiles")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "single_envfiles")))]
EnvFile(envfiles::Result<Type>),
}
impl<Type> Source<Type>
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.
///
/// # 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::multi::Source;
///
/// let value = Source::<u8>::EnvFile(Ok(1)).expect("value to be present");
/// assert_eq!(value, 1)
/// ```
///
/// ```should_panic
/// use micronfig::multi::Source;
/// use micronfig::single::envfiles::Error as FileError;
///
/// let value = Source::<u8>::EnvFile(Err(FileError::CannotReadEnvVar(std::env::VarError::NotPresent))).expect("value to be present");
/// // Panic!
/// ```
pub fn expect(self, msg: &str) -> Type {
match self {
#[cfg(feature = "single_envvars")]
Self::EnvVar(Ok(v)) => v,
#[cfg(feature = "single_envfiles")]
Self::EnvFile(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`].
///
/// # See also
///
/// Similar to [`Result::unwrap`].
///
/// Internally, it uses [`Self::expect`].
///
/// # Examples
///
/// ```
/// use micronfig::multi::Source;
///
/// let value = Source::<u8>::EnvFile(Ok(1)).unwrap();
/// assert_eq!(value, 1)
/// ```
///
/// ```should_panic
/// use micronfig::multi::Source;
/// use micronfig::single::envfiles::Error as FileError;
///
/// let value = Source::<u8>::EnvFile(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::testing::tempfile_fixture;
#[test]
#[cfg(feature = "single_envvars")]
fn it_works_var() {
std::env::set_var("NUMBER", "1");
std::env::remove_var("NUMBER_FILE");
match get::<&str, &str, u32>("NUMBER", "_FILE") {
Source::EnvVar(Ok(1u32)) => {},
_ => panic!("expected Source::EnvVar(Ok(1u32))")
}
}
#[test]
#[cfg(feature = "single_envfiles")]
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", "_FILE");
match n {
Source::EnvFile(Ok(1u32)) => {},
_ => panic!("expected Source::EnvFile(Ok(1u32))")
}
}
#[test]
#[cfg(feature = "single_envvars")]
fn missing_envvar() {
match get::<&str, &str, String>("MISSING_ENVVAR", "_FILE") {
Source::NotFound => {},
_ => panic!("expected Source::NotFound"),
}
}
#[test]
#[cfg(feature = "single_envfiles")]
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", "_FILE") {
Source::EnvFile(Err(envfiles::Error::CannotOpenFile(_))) => {},
_ => panic!("expected Source::EnvFile(Err(envfiles::Error::CannotOpenFile(_)))"),
}
}
#[test]
#[cfg(feature = "single_envvars")]
fn not_a_number_var() {
std::env::set_var("NUMBER", "XYZ");
std::env::remove_var("NUMBER_FILE");
match get::<&str, &str, u32>("NUMBER", "_FILE") {
Source::EnvVar(Err(envvars::Error::CannotConvertValue(_))) => {},
_ => panic!("expected Source::EnvVar(Err(envvars::Error::CannotConvertValue(_)))"),
}
}
#[test]
#[cfg(feature = "single_envfiles")]
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", "_FILE") {
Source::EnvFile(Err(envfiles::Error::CannotConvertValue(_))) => {},
_ => panic!("expected Source::EnvFile(Err(envfiles::Error::CannotConvertValue(_)))"),
}
}
#[test]
#[cfg(feature = "single_envvars")]
fn unwrap_var_ok() {
Source::EnvVar(Ok("ok".to_string())).unwrap();
}
#[test]
#[cfg(feature = "single_envfiles")]
fn unwrap_file_ok() {
Source::EnvFile(Ok("ok".to_string())).unwrap();
}
#[test]
#[should_panic]
#[cfg(feature = "single_envvars")]
fn unwrap_var_err() {
Source::<String>::EnvVar(Err(envvars::Error::CannotReadEnvVar(std::env::VarError::NotPresent))).unwrap();
}
#[test]
#[should_panic]
#[cfg(feature = "single_envfiles")]
fn unwrap_file_err() {
Source::<String>::EnvFile(Err(envfiles::Error::CannotReadEnvVar(std::env::VarError::NotPresent))).unwrap();
}
}

View file

@ -1,137 +0,0 @@
//! Contents of files at paths defined by environment variables.
/// Get a configuration value from the source.
///
/// # Process
///
/// This function:
///
/// 1. tries to access the environment variable with the given name using [`std::env::var`]
/// 2. tries to interpret the contents of the environment variable as a [`std::path::PathBuf`]
/// 3. tries to [`std::fs::File::open`] the file at that path
/// 4. tries to [`std::io::Read::read_to_string`] the contents of the opened file
/// 5. tries to convert the obtained value to another of the given type using [`std::str::FromStr::from_str`]
///
/// # Examples
///
/// Retrieve a configuration value from the `USER_FILE` file, maintaining it as a [`String`]:
/// ```
/// use micronfig::single::envfiles::get;
///
/// # let filename = micronfig::testing::tempfile_fixture("steffo");
/// # std::env::set_var("USER_FILE", filename.as_os_str());
/// let user: String = get("USER_FILE").expect("USER_FILE envvar to be defined");
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS_FILE` file, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// use micronfig::single::envfiles::get;
///
/// # let filename = micronfig::testing::tempfile_fixture("192.168.1.1");
/// # std::env::set_var("IP_ADDRESS_FILE", filename.as_os_str());
/// let ip_addr: IpAddr = get("IP_ADDRESS_FILE").expect("IP_ADDRESS_FILE envvar to be defined");
/// ```
///
pub fn get<Key, Type>(key: Key) -> Result<Type>
where Key: AsRef<std::ffi::OsStr>,
Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
let path = std::env::var(key)
.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(std::fmt::Debug)]
pub enum Error<ConversionError>
where ConversionError: std::fmt::Debug,
{
/// The environment variable could not be read.
///
/// Encountered when the call to [`std::env::var`] fails.
CannotReadEnvVar(std::env::VarError),
/// 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),
/// The specified file could not be read.
///
/// Encountered when the call to [`std::io::Read::read_to_string`] fails.
CannotReadFile(std::io::Error),
/// The value could not be converted to the desired type.
///
/// Encountered when the call to [`std::str::FromStr::from_str`] fails.
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)]
pub(crate) mod tests {
use super::*;
use crate::testing::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() {
std::env::remove_var("THIS_ENVVAR_DOES_NOT_EXIST_FILE");
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,99 +0,0 @@
//! Environment variables.
/// Get a configuration value from the source.
///
/// # Process
///
/// This function:
///
/// 1. tries to access the environment variable with the given name using [`std::env::var`]
/// 2. tries to convert the obtained value to another of the given type using [`std::str::FromStr::from_str`]
///
/// # Examples
///
/// Retrieve a configuration value from the `USER` environment variable, maintaining it as a [`String`]:
/// ```
/// use micronfig::single::envvars::get;
///
/// # std::env::set_var("USER", "steffo");
/// let user: String = get("USER").expect("USER envvar to be defined");
/// ```
///
/// Retrieve a configuration value from the `IP_ADDRESS` environment variable, then try to convert it to a [`std::net::IpAddr`]:
/// ```
/// use std::net::IpAddr;
/// use micronfig::single::envvars::get;
///
/// # std::env::set_var("IP_ADDRESS", "192.168.1.1");
/// let ip_addr: IpAddr = get("IP_ADDRESS").expect("IP_ADDRESS envvar to be defined");
/// ```
///
pub fn get<Key, Type>(key: Key) -> Result<Type>
where Key: AsRef<std::ffi::OsStr>,
Type: std::str::FromStr,
<Type as std::str::FromStr>::Err: std::fmt::Debug,
{
let data = std::env::var(key)
.map_err(Error::CannotReadEnvVar)?;
let value = Type::from_str(&data)
.map_err(Error::CannotConvertValue)?;
Ok(value)
}
/// A possible error encountered by [`get`].
#[derive(std::fmt::Debug)]
pub enum Error<ConversionError>
where ConversionError: std::fmt::Debug,
{
/// The environment variable could not be read.
///
/// Encountered when the call to [`std::env::var`] fails.
CannotReadEnvVar(std::env::VarError),
/// The value could not be converted to the desired type.
///
/// Encountered when the call to [`std::str::FromStr::from_str`] fails.
CannotConvertValue(ConversionError),
}
/// The result of [`get`].
pub type Result<Type> = std::result::Result<Type, Error<<Type as std::str::FromStr>::Err>>;
#[cfg(test)]
pub(crate) 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() {
std::env::remove_var("THIS_ENVVAR_DOES_NOT_EXIST");
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(_)"),
}
}
}

View file

@ -1,15 +0,0 @@
//! Lowest-level API — Manually select configuration sources.
//!
//! It can be useful if you want to specify manually the sources to access when retrieving configuration values.
//!
//! Each possible source has an associated module, and a feature named `single_{MODULENAME}` enabling it; see the list of modules below to see what sources are available!
#[cfg(feature = "single_envvars")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "single_envvars")))]
pub mod envvars;
#[cfg(feature = "single_envfiles")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "single_envfiles")))]
pub mod envfiles;

View file

@ -1,17 +0,0 @@
//! Fixtures for testing.
//!
//! **Unstable**; not supposed to be used outside this crate; do not add `pub(crate)` or doctests will stop working.
/// Create a temporary file and write `content` inside it.
///
/// The file will be deleted as soon as the [`tempfile::TempPath`] is dropped.
pub 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()
}

View file

@ -1,17 +0,0 @@
use std::env;
micronfig::required!(PLAYER_NAME, String);
micronfig::required!(PLAYER_ID, u64);
micronfig::optional!(IS_SUS, bool);
#[test]
fn test_macros() {
env::set_var("PLAYER_NAME", "Steffo");
env::set_var("PLAYER_ID", "1234");
env::remove_var("IS_SUS");
assert_eq!(*PLAYER_NAME, "Steffo");
assert_eq!(*PLAYER_ID, 1234u64);
assert_eq!(*IS_SUS, None);
}

View file

@ -1 +0,0 @@
pub mod macros;