Expand description
mkenv is a lightweight crate that helps you define the configuration your application uses.
The configuration is based on environment variables, just like you would fetch them directly
with the std::env module. mkenv provides utility items to help you centralize the
environment variables your program needs to run.
One benefit of this design is to make your code cleaner when it’s about reading an environment variable. It helps you fetch it from anywhere in your code.
Another benefit is, by centralizing the definitions of the configuration your app uses, you can initialize the instance on startup (in fact, at any moment you want), and it will throw an error if any environment variable is invalid.
Note: due to a design change, items from version 0.1 are deprecated. They will probably be removed in a future version.
Better to see this through some examples:
§Examples
The basic usage of this library is with the make_config! macro:
use mkenv::{prelude::*};
mkenv::make_config! {
struct AppConfig {
user: { var_name: "USER" },
pwd: { var_name: "PWD" },
}
}
let config = AppConfig::define();This example generates a struct with fields user and pwd, having types that allow you to
fetch environment variables returning a String.
Later in your code, you may fetch the $USER or $PWD environment variables like this:
let user = config.user.get();
let pwd = config.pwd.get();You may also check for their availability at any time, e.g. when your program starts:
fn main() {
let config = AppConfig::define();
config.init();
}If the init() call fails, meaning it couldn’t retrieve the required environment variables,
then a message similar to this is shown:
Cannot initialize environment:
Got 1 incorrect variable
- `PWD`: environment variable not found
Got 1 valid variable
- `USER`
Note: full required environment description:
- `USER`
- `PWD`§Features
§Layers
Layers are an abstraction of the way in which an environment variable is retrieved. Indeed,
environment variables aren’t simply texts (Strings). They could be a number, a date, or even
be optional, or represent the content of a file.
Layers are represented by the Layer trait. The base layer is TextVar. Its output
type is String, and it simply returns the content of the environment variable.
You can wrap this base layer with another one, for example with file_read(). The output
type will still be String, but the value will be the content of the file pointed at the path
of the environment variable:
make_config! {
struct ConfigWithLayers {
file_content: {
var_name: "FILE_PATH",
layers: [file_read()],
}
}
}
let config = ConfigWithLayers::define();
// Reads the content of $FILE_PATH
let content = config.file_content.get();You may also parse the value of an environment variable, with the parsed() layer:
make_config! {
struct ConfigWithLayers {
timeout: {
var_name: "REQUEST_TIMEOUT",
layers: [
parsed<Duration>(|input| {
input.parse::<u64>()
.map(Duration::from_millis)
.map_err(From::from)
}),
],
}
}
}You may have guessed it, but layers are designed to be combined together. This means you can define a configuration value to be parsed from the content of a file:
make_config! {
struct ConfigWithLayers {
whatever_number: {
var_name: "WHATEVER_NUMBER",
layers: [
file_read(),
parsed_from_str<i32>(),
],
}
}
}Find out more about layers in the module documentation.
§Composable declarations
The make_config! macro supports composable declarations, meaning including the declaration
of other configuration types into another. See the example:
make_config! {
struct DbConfig {
db_url: { var_name: "DB_URL" },
}
}
make_config! {
struct AppConfig {
db_config: { DbConfig },
}
}The AppConfig struct will have a field db_config: DbConfig. Its initialization will include
the one of the DbConfig struct, meaning all the environment variables the DbConfig struct
needs, are also needed by the AppConfig struct.
You may also use the composable pattern for conditional purposes:
#[cfg(debug_assertions)]
make_config! {
struct DbConfig {
db_url: { var_name: "DB_URL" },
}
}
#[cfg(not(debug_assertions))]
make_config! {
struct DbConfig {
db_url: {
var_name: "DB_URL_FILE",
layers: [file_read()],
},
}
}
make_config! {
struct AppConfig {
db_config: { DbConfig },
}
}§Lightness
The library is very light, it has 0 dependency!
§Migration from v0.1
Please use the make_config! macro instead of the old make_env!. It generates much less
code, and will make your binaries lighter.
mkenv::make_env! {AppEnv:
db_url: {
id: DbUrl(String),
kind: normal,
var: "DB_URL",
desc: "The URL to the database",
}
}becomes:
mkenv::make_config! {
struct AppEnv {
db_url: {
var_name: "DB_URL",
description: "The URL to the database",
}
}
}The old kind key in the macro is transformed into a newer layers key:
- The
normalkind is the base layer type, no need to specify it. - The
parsekind is the newparsed()layer, orparsed_from_str(). - The
filekind is the newfile_read()layer. - There is no more concept of “wrapping types” with the
parsekind. You can already provide a custom parse function. - Default values are now represented by a new layer:
or_default_val()andor_default(). So there is no need to provide an identifier for the default value anymore, which was forcing you to declare a const in the first place. - Conditional compilation isn’t supported at field level anymore. The reason for this is that
the expansion would have been way more complex, especially for the
Iterassociated type of theConfigInitializertrait (whose implementation is generated by the newmake_config!macro). However, conditional compilation is possible to perform, but at the macro call level, and using the composable pattern, as mentioned before. - New composable pattern is way more flexible. Included configurations are simple fields of the output struct, and you can declare them in the middle of other regular fields.
- A critical note about security: the new design doesn’t need a way to “split” structs anymore
(previously represented by the
EnvSplitIncludedtrait). This is because the previous design was storing the value of the environment variables everytime. Whereas in the new design, caching has become its own layer. By default, environment variables are fetched only on call (when calling thetry_get()orget()methods of theLayertrait). In short, security is no longer a concern! 🎉
Modules§
- error
- Contains all error types.
- exec
- Contains everything related to the execution of a full read of a configuration.
- layers
- Module containing configuration value types.
- prelude
- Utility module importing the most relevant types and traits.
Macros§
- init_
env Deprecated - make_
config - Generates the definition of a configuration value set.
- make_
env Deprecated - Generates the definition of an environment configuration.
Structs§
- Captured
Vars Deprecated - EnvError
Deprecated - VarDescriptor
- Describes a configuration value.
- With
Vars Deprecated
Enums§
- Error
Deprecated
Traits§
- Config
Descriptor - Represents types able to describe a set of configuration values.
- Config
Value Descriptor - Represents types able to describe a configuration value.
- Env
Deprecated - EnvResidual
Deprecated - EnvSplit
Included Deprecated - EnvVar
Deprecated - FmtReq
Env Deprecated - Layer
- Represents types able to read a value from the process environment.
- Layer
Ext - Utility trait for building configuration value types.