Crate mkenv

Crate mkenv 

Source
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 normal kind is the base layer type, no need to specify it.
  • The parse kind is the new parsed() layer, or parsed_from_str().
  • The file kind is the new file_read() layer.
  • There is no more concept of “wrapping types” with the parse kind. You can already provide a custom parse function.
  • Default values are now represented by a new layer: or_default_val() and or_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 Iter associated type of the ConfigInitializer trait (whose implementation is generated by the new make_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 EnvSplitIncluded trait). 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 the try_get() or get() methods of the Layer trait). 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_envDeprecated
make_config
Generates the definition of a configuration value set.
make_envDeprecated
Generates the definition of an environment configuration.

Structs§

CapturedVarsDeprecated
EnvErrorDeprecated
VarDescriptor
Describes a configuration value.
WithVarsDeprecated

Enums§

ErrorDeprecated

Traits§

ConfigDescriptor
Represents types able to describe a set of configuration values.
ConfigValueDescriptor
Represents types able to describe a configuration value.
EnvDeprecated
EnvResidualDeprecated
EnvSplitIncludedDeprecated
EnvVarDeprecated
FmtReqEnvDeprecated
Layer
Represents types able to read a value from the process environment.
LayerExt
Utility trait for building configuration value types.