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:
parent
825a20b19a
commit
c6df6cb3af
16 changed files with 0 additions and 1070 deletions
|
@ -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 = "../.." }
|
|
@ -1,9 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
|
||||
micronfig::required!(ECHO, String);
|
||||
|
||||
|
||||
fn main() {
|
||||
println!("{}", *ECHO);
|
||||
}
|
|
@ -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 = "../.." }
|
|
@ -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 => "/",
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 = "../.." }
|
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
||||
}
|
||||
}
|
52
src/lib.rs
52
src/lib.rs
|
@ -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;
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
293
src/multi/mod.rs
293
src/multi/mod.rs
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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(_))"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(_)"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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()
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
pub mod macros;
|
Loading…
Reference in a new issue