envfmt v1.0.0

A while ago, I wrote about releasing the first version of envfmt. It was a tiny library I built to solve a problem in another project, and it turned into a great lesson on using Rust’s traits for testing.

Today, I’m excited to release envfmt as v1.0.0.

This isn’t a release packed with new features. In fact, it’s the opposite. The goal of this release was to simplify, stabilize, and polish what was already there. It’s about making the crate a small, dependable tool that you can forget about once you add it to your project.

There are three main updates I want to talk about.

New Documentation Site

The auto-generated docs from cargo doc are amazing, but I wanted a place to show more practical examples and explain the library’s purpose more clearly.

So I built a documentation site with Astro. It covers why you might want to use envfmt and how to use its API, with copy-pasteable examples. Writing good documentation is a skill I’m trying to improve, and this was a fun little exercise.

Zero Dependencies

In my last post, I talked about how much I enjoyed using thiserror to handle errors. It was clean and made defining custom error types very easy.

But envfmt is a tiny library. Its whole purpose is to do one small thing with minimal fuss. As I thought about it more, even a single, small dependency felt like it went against that spirit. I wanted it to be completely self-contained.

So, I removed thiserror.

This meant I had to implement the standard library’s std::error::Error and std::fmt::Display traits myself. Here’s what the Error enum looks like now:

src/lib.rs
/// Represents errors that can occur during formatting.
#[derive(Debug, PartialEq)]
pub enum Error {
    /// A required variable was not found in the context.
    VariableNotFound(String),

    /// A variable name was invalid, e.g `${}`.
    InvalidVariableName(String),

    /// The input string have an unclosed brace.
    UnclosedBrace,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::VariableNotFound(var) => {
                write!(f, "variable not found: '{}'", var)
            }
            Error::InvalidVariableName(var) => {
                write!(f, "invalid variable name: '{}'", var)
            }
            Error::UnclosedBrace => {
                write!(f, "unexpected end of input: missing closing brace '}}'")
            }
        }
    }
}

impl std::error::Error for Error {}

It’s a little more code than the thiserror version, for sure. But it’s not complicated. And in return, envfmt now has zero dependencies. It’s just my code and the Rust standard library. For a utility this small, that trade-off feels right. It was a good reminder that helper crates are great, but sometimes just using the standard library directly is the simplest solution.

A Stable API

The final reason for the 1.0.0 release is to make a promise. The API is now stable.

The core functions, format and format_with, and the Context trait are solid. I don’t see them changing. By releasing a v1.0.0, I’m saying that you can depend on envfmt and not worry about a cargo update breaking your build. For a low-level tool, that kind of stability is the most important feature.

And that’s it. envfmt is a complete, tiny, and now stable tool. It does one thing, and I hope it does it well. It was a fantastic learning project for me, and I’m happy to finally call it “done”.

You can find the crate on crates.io, the code on GitHub, and the new docs at pyk.sh/envfmt.