gifnoc
Type-safe and layered project configuration from multiple sources
Motivation
Using various third-party tools, I often dispair when trying to configure them. Some options can only be set via a command-line flag, some are read from a config file, while still others can only be set via environment variables. These ambiguities are exacerbated when using third-party tools in docker or docker compose.
At the same time, I know from personal experience how tedious it can be to maintain a well-documented set of configuration options, especially in a fast moving world, where the code base changes on a daily basis and quick experiments need to be run on short notice.
I have implemented a solution in python that I use in all my projects. It
can be found in the cli and jsonobject subpackages of
swak, available from the python
package index PyPI. Working mostly in
data-science and machine learning and, therefore, using
pola.rs for data pipelines, the gifnoc crate
(config in reverse), is my attempt to replicate a pragmatic project
configuration solution in rust.
Design
- There is one and only one global project configuration.
- Every configuration option has a default.
- The project configuration has the form of a (nested)
struct. - All configuration options can be set by all mechanisms:
- command-line arguments
- environment variables
- configuration file (TOML or YAML)
- The precedence of these update is a matter of choice and taste.
- Adding fields to the (nested) config struct(s) is the only code change required to make a new option available to all mechanisms.
Usage Example
Consider the following (simplified) main.rs of your rust application yourapp.
use ;
// Define defaults
config!
config!
config!
config!
Part of your settings could be in a config.toml.
[]
= 8888
[]
= "/api/v1"
The other parts could be in the form of environment variables or command-line flags (in long form only).
APP_SERVER__PORT=9000
Note the use of double underscore in the environment variable name to indicate
nesting versus the dot.separation to indicate nesting for the command-line
flag. With the chosen order of precedence, the server's port would now be
9000 and the route's API prefix would be "/api/v2". The positional arguments
step1 and step2 are free for use as you see fit.