This month in Pavex, #9
- 1499 words
- 8 min
๐ 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
- Documentation
- Version management
- Kits
pavex_tracing
- Error observers
- Terser registrations
- CI
- Windows
- Miscellaneous
- What's next?
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:
- a command to uninstall Pavex and clear its cache data (
pavex self uninstall
) - a command to update to the latest released version of the CLI (
pavex self update
)
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:
- You no longer need to use a fully-qualified path
(e.g.
crate::request::path::PathParams::extract
) to register a component. You can use a relative path that starts with eitherself
(relative to the current module) orsuper
(relative to the parent module). - You can use a lifecycle-specific shorthand when registering constructors (e.g.
Blueprint::request_scoped
).
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:
- We now have universal error handlers
- Our user-facing errors have improved
- Pavex now works nicely with
cargo-watch
- Pavex emits a warning if you register a constructor that's never used
- Better configuration setup for the starter project
- ...and a lot of bug fixes.
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.
The recording has recently been uploaded to YouTube.
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.