I've worked with config files for a long time. Be it for my own tools/projects or for using it for other purposes, I have needed to manage configs manually for a long time. Until I discovered that there exist literal config managers, and that I had been wasting my efforts for absolutely no reason.
I found that there were a bunch of good options available for config managers in JavaScript (NPM). However, the same is not the case for Rust. Here, there are limited options, popular ones being config
, config-manager
and figment
. And so I set out to work on a new project, and here it is:
What is Profig?
Introducing Profig, a config framework, that removes the Con in Config. This is still in very early development stages, and I've only released just v0.1
, but I think it's still usable in small and maybe even medium sized projects.
Features
Profig is currently v0.1
, and it has the following features:
- Macros like
#[derive(Profig)]
and#[profig(...)]
to define config schema structs - Enforce only certain file formats to be allowed to load data from, using
#[profig(format="json,toml")]
- Every field of the struct will have its own metadata, defined via
#[profig(min="..", max="..", default="..", regex=r"..", doc="..")]
- Data can be loaded from any file using
AppConfig::load("filename.ext")
(assuming AppConfig is the name of the struct defined), and it returns an object of type AppConfig - It validates every field data according to the metadata (
min
,max
,regex
etc.) and fills a field with thedefault
value if applicable - Sample config files can be generated via
AppConfig::sample_config("sample.yaml")
using the data available in the metadata of the fields (defined using#[profig(...)]
) - Documentation can be generated automatically using
AppConfig::generate_docs("docs.md")
which generates a markdown file using thedoc
metadata of every field - Multiple formats: JSON, TOML, YAML (all as their own features)
Usage
All these features can seem confusing and overwhelming at first, even though they aim to greatly enhance DX. So, let's do a bit of practical.
Let's imagine a situation where we need the user to define a config file which contains some data like host name, port number, user email, and the number of worker threads. The user may have a JSON or a YAML file as a config file. Using this data, our Rust code is supposed to authenticate/validate the details and then start a server or do something else, as required. Suppose that we are to build a CLI, and we need the details from a config file, as mentioned. Let's see how simple it is to achieve with Profig.
Before anything else, let us first add the crate to our project. Since we need JSON and YAML configs, we will enable those features while installing.
cargo add profig --features "json,yaml"
First, we define a schema for the config file. To define the schema, we use the attributes provided by Profig as follows:
#[derive(Profig)]
#[profig(format="json, yaml")]
struct HostConfig {
#[profig(min="4", max="10", doc="Number of worker threads")]
threads: u32,
#[profig(default="localhost", doc="Name of the host")]
hostname: Option<String>,
#[profig(min="1", max="65536", doc="Port number to listen to")]
port: u32,
#[profig(regex=r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", doc="Email address of the user")]
email: String
}
This is all we need to do in order to define the schema/format of the config files. We specified formats as json
and yaml
as those are the only ones allowed. Now to read data from a file, say config.yaml
, we can do so as follows:
fn main() -> Result<(), Box<dyn std::error::Error>> {
let confData = HostConfig::load("config.yaml")?;
// All data can now be accessed as confData.email, confData.host etc
// because this returns an object of type HostConfig.
// You can use this data for any further logic implementation
}
And voila! You have successfully loaded and used data from config files!
Sample Config
You can also generate a sample config file in a single line, using the following function:
HostConfig::sample_config("sample.json")?;
This will create the following type of sample file (JSON here):
{
"threads": 4,
"hostname": "localhost",
"port": 1,
"email": "REQUIRED; must match ^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$"
}
Documentation Generation
That's not all. You can also generate a documentation for your defined config structure in a single line! Just call the following method:
HostConfig::generate_doc("docs.md")?;
And it will generate you a markdown
file filled with data like field name, field data type, and a field help/doc line if provided (via the doc="..."
value in the field-level metadata).
🛠 Tech Dive
For the bit more nerdy humans like myself, let's talk a bit about the tech. This project uses Rust (no s**t Sherlock), along with:
- Serde, serde_json for storing and validating the data
- serde_json, serde_yaml and toml crates to load data from different config file types
- Procedural macros to make the
#[derive(Profig)]
and#[profig(...)]
attributes to make DX smoother - Feature flags for every file format to avoid unnecessarily bloating the crate
This is an architecture/design of the entire working or flow:
Final Notes
All in all, Profig is a config framework that I plan to keep building and improving as time sees. It is in very early stages right now, being just v0.1
, so there will be quite some changes before even Profig v1
is launched.
In the meanwhile, I would love any and all feedbacks and advice.
Please do try it for yourself if possible:
🦀 Crates.io
💻 GitHub
Top comments (0)