mirror of
https://github.com/Steffo99/micronfig.git
synced 2024-11-22 16:14:19 +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