This month in Pavex, #8: docs, docs, docs
- 1599 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 December!
At the end of November, I announced that Pavex was entering a closed beta period.
From a functionality perspective, the framework was ready for a test drive.
Documentation, however, was lacking.
That's where most of December went: docs, docs, docs.
A month later, we're in a good place1. Good enough to kick off the closed beta2: the first batch of invites went
out on January 3rd!
I took a different approach to Pavex's documentation compared to my previous OSS projects and the work I did in "Zero to Production in Rust". I'll use the rest of this post to dive into it!
You can discuss this update on r/rust.
Table of Contents
Documentation is hard
No docs, no users
Documentation is one of the most important parts of a project.
There's plenty of great projects out there that aren't realising their full potential
because people don't know how to use them.
I've always admired the Rust community's emphasis on documentation.
Compared to other ecosystems, the average crate has better docs.
That's definitely been a factor in Rust's success and a testament to the excellent work done by the
rustdoc
team: more maintainers are willing to write docs if it's easy and they don't
have to worry about hosting.
What docs do you need?
What makes documentation good? It's a tough question to answer!
You hear people praising this or that project's documentation, but it's often hard to articulate why it's good.
While working at AWS, I was introduced to Diátaxis, a systematic framework for writing documentation. It starts with a simple premise: documentation must serve the needs of its users. It then goes on to identify those needs and define different types of documentation components that can be used to address them.
I leaned heavily on Diátaxis when structuring Pavex's documentation.
rustdoc
is great, but it's not enough
The Rust community has a great tool for writing documentation: rustdoc
.
It's great at what it does: writing a comprehensive (and well-tested) API reference.
But users need more than an API reference!
They need tutorials, teaching the basics as they walk you through a series of scripted steps.
They need high-level explanations, covering the framework's design and how everything fits together.
They need how-to guides, tailored to the problem they're trying to solve.
All these things are hard to write in rustdoc
because rustdoc
is code-oriented.
Everything has to be attached to a specific Rust item, be it a module or a type.
The module hierarchy becomes the documentation hierarchy, even though it's not necessarily
the best way to present the information to the user.
You see Rust crates adding fake modules just to attach high-level documentation somewhere.
Pavex's documentation
For Pavex, I decided to augment the API reference generated by rustdoc
with
a separate documentation site1.
It's built with Material for MkDocs, another
incredible tool for writing documentation.
The rustdoc
API reference is still there, but it's just a part of the whole picture.
And that picture is big! In just over a month, we have:
- ~800 lines of code examples
- ~2000 lines of explanations, guides and tutorials
on top of the existing API reference and its doc examples.
Stale docs are the worst
One of the biggest challenges with documentation is keeping it up to date.
It's incredibly frustrating to follow a tutorial and get stuck because the code that's
being shown doesn't compile anymore.
rustdoc
is great at that: all code examples are compiled and tested every time you run cargo test
.
Things get a lot more complicated when you're writing tutorials and guides.
You are guiding the user through a learning journey and the code changes at every step as you
progress through the tutorial project.
This is a challenge I'm familiar with from my work on "Zero to Production in Rust": you can
think of the book as a 600-page-long tutorial.
When I update a code snippet, I have to make sure I've updated all the other snippets that
reference it or relate to it in a later section or chapter.
It's tricky and error-prone.
I wanted a better solution for Pavex.
Test all the things
Every section of Pavex's documentation site is backed by a YAML manifest.
In the manifest, we specify:
- The state of the code at the beginning of the section (or a command to generate it)
starter_project_folder: "project"
- The code snippets that we want to extract from the project to be shown in the documentation
snippets: - name: "blueprint_definition" source_path: "demo/src/blueprint.rs" ranges: [ "8..17" ] # [...]
- Commands that we want to run on the code snippets (e.g.
cargo run
) and their expected outcomecommands: - command: "cargo px c" expected_outcome: "success" # [...]
- The changes that we want to apply to the starter project, as
git
patch filesdiff --git a/demo/src/blueprint.rs b/demo/src/blueprint.rs index e3623cd..c431f69 100644 --- a/demo/src/blueprint.rs +++ b/demo/src/blueprint.rs @@ -11,7 +11,8 @@ pub fn blueprint() -> Blueprint { bp.constructor( f!(crate::user_agent::UserAgent::extract), Lifecycle::RequestScoped, - ); + ) + .error_handler(f!(crate::user_agent::invalid_user_agent)); add_telemetry_middleware(&mut bp);
This manifest is fed into a custom tool I built, tutorial_generator
, to generate the snippet files (*.snap
)
that are then embedded into the documentation site using an mkdocs
plugin:
# Routing
## Route registration
All the routes exposed by your API must be registered with its [`Blueprint`][Blueprint].
In the snippet below you can see the registration of the `GET /api/ping` route, the one you targeted with your `curl`
request.
--8<-- "doc_examples/quickstart/demo-route_registration.snap"
If anything changes in Pavex (e.g. we modify the starter template returned by pavex new
), the snippets
will automatically reflect the changes.
Since the snippets are committed to version control, we can review the changes and make sure that they are
still correct.
If they're not, we update the YAML manifest and regenerate the snippets by rerunning the command.
In CI, instead, we just make sure that the snippets are up-to-date: if they're not, the build fails.
This system introduces a bit of complexity, but it's worth it.
I can make sweeping changes to the framework and be confident that I won't miss anything in the documentation.
In the few weeks it has been up and running it saved me more than once from leaving stale examples around!
What's next?
I'm really excited to have the closed beta up and running!
I'm looking forward to the feedback from the first cohorts of testers to help me shape the framework.
In the coming month, I'll be splitting my focus on three fronts (in order of priority):
- Stability. As more people try to use the framework, we'll find bugs3. I want to patch any showstopping issues as soon as they arise.
- Documentation. I've done a lot in December, but there's a few more sections I want to add (e.g. how to write middlewares, how to leverage
Blueprint
nesting to build modular applications, etc.) - Gap analysis. Can you migrate your existing projects to Pavex? Are we missing any core feature that would prevent you from doing so? I've already identified a few items (e.g. connection upgrades, connection info), but I want to put together a comprehensive list. And then cross them off the list, one at a time!
That's all for December, see you next month!
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 hosted version of the docs is not publicly available yet. I'll share it once we reach the open beta phase! In the meantime, you can have a peek at it on GitHub if you're curious.
New batches of invites will be sent out every few weeks. Sign up to join the beta!