This month in Pavex, #9

๐Ÿ‘‹ Hi!
It's Luca here, the author of "Zero to production in Rust".
This is a progress report about Pavex, a new Rust web framework that I have been working on.

It's time for another progress report on Pavex, covering what has been done in January and February!
There's a lot going on this time, across multiple domains: user experience, functionality, bug fixes. Easier to dive straight into the changes!

You can discuss this update on r/rust.

Table of Contents

Closed beta

At the beginning of January I started to send out invites to Pavex's closed beta.
There was a lot more excitement for Pavex than I was expecting: 479 people joined the waiting list!
To keep things manageable, I sent invites out in waves: once every ~10 days, 30 invites at a time. I'm almost halfway through the list: 183 invites have gone out in January and February, and 147 of those joined Pavex's Discord server.

The more the framework stabilizes, the faster I'll be sending invites out. If you're still waiting, thank you for your patience!

Documentation

Documentation is never finished, but after a big push it's now in a pretty good spot. It's also publicly available on Pavex's website!

Version management

When working on a Pavex project, you rely on both pavex (the CLI) and pavex (the library crate).
Weird stuff may happen if you use different versions in the same project. Cargo doesn't make it easy either: libraries are naturally scoped to a project, CLIs are global.

To solve the issue once and for all, I've re-architected Pavex's CLI. pavex is now a version manager, just like rustup for Rust's toolchain.
The "true" compiler logic has been moved to a different CLI, pavexc. When you invoke a Pavex command from inside a project, pavex will automatically determine which version of pavexc needs to be used by looking at the version of pavex in your Cargo.lock. You never interact with pavexc directly (unless you want to), just like you never deal with rustc in your day-to-day Rust work.

There have been no reports of issues related to version management ever since this was released. Success!

As a bonus, I also built:

Kits

I introduced a new concept: kits.
Kits bundle together multiple constructors commonly used together for a specific purpose. I started with ApiKit: all the first-party constructors provided by Pavex for building REST APIs.

use pavex::blueprint::Blueprint;
use pavex::kit::ApiKit;

pub fn blueprint() -> Blueprint {
  let mut bp = Blueprint::new();
  // ๐Ÿ‘‡ Path params, query params, body extractors, etc.
  ApiKit::new().register(&mut bp);
  // [...]
}

When generating a new project via pavex new, ApiKit will be automatically installed.

pavex_tracing

I've released a new crate: pavex_tracing.
It bundles RootSpan, the default logger middleware, and helper functions to log common fields according to OpenTelemetry's conventions.

Thanks to pavex_tracing there is a lot less bespoke telemetry logic in the starter project, which should make it easier to benefit from upstream improvements in the future.

Error observers

Pavex relies on error handlers to convert errors into responses.
Who is in charge of logging those errors, though?
That's the job of error observers, the latest addition to Pavex's observability story.

use pavex_tracing::fields::{
    error_details, error_message, error_source_chain, ERROR_DETAILS, ERROR_MESSAGE, ERROR_SOURCE_CHAIN 
};
use pavex_tracing::RootSpan;

/// An error observer to log error details.
///
/// It emits an error event and attaches information about the error to the root span.
/// If multiple errors are observed for the same request, it will emit multiple error events
/// but only the details of the last error will be attached to the root span.
pub async fn error_logger(e: &pavex::Error, root_span: &RootSpan) {
    tracing::event!(
        tracing::Level::ERROR,
        { ERROR_MESSAGE } = error_message(e),
        { ERROR_DETAILS } = error_details(e),
        { ERROR_SOURCE_CHAIN } = error_source_chain(e),
        "An error occurred during request handling",
    );
    root_span.record(ERROR_MESSAGE, error_message(e));
    root_span.record(ERROR_DETAILS, error_details(e));
    root_span.record(ERROR_SOURCE_CHAIN, error_source_chain(e));
}

I wrote an exhaustive post on error reporting earlier this month. Check it out to see how Pavex's approach compares to existing Rust web frameworks.

Terser registrations

I've started a little crusade against verbosity. Some things can feel boilerplate-y when working with Pavex, so I've added some sugar:

Registrations look a lot terser!

// Before ๐Ÿ˜”
bp.constructor(
   f!(crate::request::path::PathParams::extract), 
   Lifecycle::RequestScoped
)
.error_handler(
   f!(crate::request::path::errors::ExtractPathParamsError::into_response)
);

// After ๐Ÿš€
bp.request_scoped(f!(super::PathParams::extract))
  .error_handler(f!(super::errors::ExtractPathParamsError::into_response));

CI

The project generated by pavex new (that you can browse on GitHub) now includes a CI pipeline for GitHub Actions as well as a Dockerfile.

They have all the bells and whistles you might look for in a production project: good caching, linting, formatting, code coverage, staleness check for the generated code, security auditing.

Windows

Pavex's CLI depended, transitively, on OpenSSL. This created more than a few headaches to our beta testers on Windows. It turns out that installing OpenSSL on Windows is... not trivial.

Luckily enough, that's a problem of the past: I've removed the dependency that brought in OpenSSL and enhanced our CI to build and tests on Windows. I had some fun learning how to write small Powershell scriptsโ€”there's always a first time!

Miscellaneous

There's too much to cover everything in details, but a few other changes that landed:

What's next?

The dependency injection engine is getting more stable and it provides most of the functionality I think we'll need, thus I can start spending my time elsewhere. I already started this month (see the work on observability), but I'll be doubling down next month: features, features, features!
The list is quite long: cookies, sessions, TLS, compression, etc. It'll be fun! When I'm done, you should be able to build a backend + website using only Pavex's first-party crates. I'll be dogfooding it all with Pavex's website.

At the end of March I'll also be at RustNation UK to give a talk about Pavex1. It'll be an updated version of what I covered at RustLab in November2. If you're coming to the conference, happy to meet and chat about Pavex (or Rust in general).

You can discuss this update on r/rust.

Subscribe to the newsletter if you don't want to miss the next update!
You can also follow the development of Pavex on GitHub.


2

The recording has recently been uploaded to YouTube.

1

I'll also lead the expert-level workshop on testing. There are a few tickets left; if they run out, you can join the remote version in April if you're interested.