<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Luca Palmieri</title>
      <link>https://80a8f3c0.lpalmieri.pages.dev</link>
      <description>Hands-on technical leader, Rust expert.</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://80a8f3c0.lpalmieri.pages.dev/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Thu, 28 May 2026 00:00:00 +0200</lastBuildDate>
      <item>
          <title>Work</title>
          <pubDate>Thu, 28 May 2026 00:00:00 +0200</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/work/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/work/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/work/">&lt;p&gt;I work with engineering teams that need senior, hands-on technical leadership for reliable backend systems.&lt;&#x2F;p&gt;
&lt;p&gt;My strongest fit is where the work spans multiple levels at once: code quality, architecture, delivery, observability, and the engineering habits that keep systems maintainable over time. I can dive into a single line of Rust, review a service boundary, or reason through the trade-offs in a large distributed architecture.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-i-can-help&quot;&gt;How I can help&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rust backend systems&lt;&#x2F;strong&gt;: design, implementation, reviews, testing strategy, observability, and production readiness.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Legacy migrations&lt;&#x2F;strong&gt;: pragmatic paths from existing systems to Rust, with attention to risk, team adoption, and operational continuity.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Technical leadership for delivery&lt;&#x2F;strong&gt;: architecture reviews, quality bar setting, cross-team alignment, and mentoring for senior ICs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Workshops and advisory&lt;&#x2F;strong&gt;: focused sessions on Rust, backend engineering, testing, telemetry, and maintainable service design.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I am the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production in Rust&lt;&#x2F;a&gt;, a Principal Engineer at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mainmatter.com&quot;&gt;Mainmatter&lt;&#x2F;a&gt;, and I previously worked at TrueLayer and AWS.&lt;&#x2F;p&gt;
&lt;p&gt;If that sounds like the kind of help you need, &lt;a href=&quot;mailto:contact@lpalmieri.com&quot;&gt;email me&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Can agentic coding raise the quality bar?</title>
          <pubDate>Sat, 15 Feb 2025 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/agentic-coding-raises-quality/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/agentic-coding-raises-quality/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/agentic-coding-raises-quality/">&lt;p&gt;The mainstream discourse around agentic coding focuses on &lt;strong&gt;throughput&lt;&#x2F;strong&gt;: ship faster, write more code, do more with less (headcount). And that&#x27;s indeed how AI tooling adoption is playing out in many organizations. But there&#x27;s more than one playbook in town!&lt;&#x2F;p&gt;
&lt;p&gt;In many software systems, &lt;strong&gt;quality&lt;&#x2F;strong&gt; matters: payment rails, databases, infrastructure control planes. They must be highly available, performant, trustworthy. In these systems, quality can be &lt;strong&gt;measured&lt;&#x2F;strong&gt; in units that can ultimately be converted to $$$.
Those are the kind of systems I worked on throughout my career. And in these systems, &lt;strong&gt;agentic coding can be used to raise the quality bar&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#the-shift&quot;&gt;The shift&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#example-1-more-tooling&quot;&gt;Example 1: More tooling&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#example-2-prototype-to-discover-constraints&quot;&gt;Example 2: Prototype to discover constraints&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#example-3-build-to-compare&quot;&gt;Example 3: Build to compare&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#example-4-low-value-per-line-abstractions&quot;&gt;Example 4: Low value-per-line abstractions&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#example-5-pay-off-tech-debt-eagerly&quot;&gt;Example 5: Pay off tech debt eagerly&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;agentic-coding-raises-quality&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;the-shift&quot;&gt;The shift&lt;&#x2F;h1&gt;
&lt;p&gt;Most of the processes and rituals in the world of software development are designed around a single core belief: code is &lt;strong&gt;expensive&lt;&#x2F;strong&gt;. Software requires highly specialized workers, it takes an unpredictably long time, and changing your mind halfway through a project is likely to have disastrous impact on the economic viability of the whole endeavour.&lt;&#x2F;p&gt;
&lt;p&gt;Agentic coding challenges that premise. &lt;strong&gt;Agent code is cheap&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
The challenge is integration: the output of the current generation of models can’t be blindly trusted, especially on production-critical paths. The effort shifts: verification become the dominant cost, which caps how much agentic work you can safely deploy at once.&lt;br &#x2F;&gt;
Nonetheless, there’s an interesting quadrant where agentic workflows have no real downsides:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Time-consuming work with cheap verification&lt;&#x2F;li&gt;
&lt;li&gt;Low-blast-radius problems where partial or approximate solutions are still useful&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And there’s a lot to do in that quadrant! Tasks and practices that were &quot;not worth it&quot; when every line had to be hand-written are suddenly economically viable, and they can leveraged to raise the quality bar of the entire system.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll cover a few concrete examples from my own experiments: cases where agentic workflows changed the cost calculus enough to justify doing more quality-related work than I used to.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;example-1-more-tooling&quot;&gt;Example 1: More tooling&lt;&#x2F;h1&gt;
&lt;p&gt;Most engineering processes suffer from annoying papercuts that nobody bothers to fix. A quality metric you&#x27;d like to track, a code pattern you&#x27;d like to enforce (or forbid!). Those tasks rot in the backlog: they&#x27;re not hard, but they&#x27;re neither urgent nor do they deliver enough value to be prioritized over feature work.&lt;&#x2F;p&gt;
&lt;p&gt;The cost equation changes when an agent can knock out a good-enough version with minimal input, and it&#x27;s cheap to verify whether it works or not.
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;RediSearch&#x2F;RediSearch&#x2F;pull&#x2F;8121&quot;&gt;Last month, I used an agent to build a CLI that tracks the ratio of safe&#x2F;unsafe code on a crate-by-crate basis&lt;&#x2F;a&gt;. It had been on my mental to-do list for &lt;em&gt;ages&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The quality of the primary system improves thanks to guardrails and tools that, under normal circumstances, wouldn&#x27;t get built at all.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;example-2-prototype-to-discover-constraints&quot;&gt;Example 2: Prototype to discover constraints&lt;&#x2F;h1&gt;
&lt;p&gt;There&#x27;s a recurring dream in engineering management: the &lt;strong&gt;perfect specification&lt;&#x2F;strong&gt;. The belief that, with enough upfront analysis, all constraints and challenges can be discovered before writing a single line of code. No surprises during execution.
We&#x27;re seeing a comeback of this idea in the AI age under the banner of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;martinfowler.com&#x2F;articles&#x2F;exploring-gen-ai&#x2F;sdd-3-tools.html&quot;&gt;spec-driven development&lt;&#x2F;a&gt;: put a lot effort into writing a detailed specification, then let the agent execute it in one shot. Same dream, different age.&lt;&#x2F;p&gt;
&lt;p&gt;I see a rather different opportunity. Rather than going back to waterfall, &lt;strong&gt;push iterative prototyping further&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Do just enough upfront design to kick off an agentic experiment. Observe where the agent gets stuck or confused: those are constraints your design failed to account for. Note them down, fold them back into the design draft, restart.
Repeat until you have a walking skeleton, then you start iterating and polishing.&lt;&#x2F;p&gt;
&lt;p&gt;AI agents become a mechanism to &lt;strong&gt;map the problem space&lt;&#x2F;strong&gt;. Each failed attempt surfaces something real&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#failures-are-artifacts&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;: an API that doesn&#x27;t behave as expected, a concurrency issue you didn&#x27;t foresee, old piles of tech debt getting in the way. You &lt;em&gt;could&lt;&#x2F;em&gt; discover many of these issues through careful upfront analysis, but cheap executable probes tend to reveal them faster, because they force the design to confront the real system earlier in the process.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;example-3-build-to-compare&quot;&gt;Example 3: Build to compare&lt;&#x2F;h1&gt;
&lt;p&gt;In some cases, you know the design constraints and you have narrowed down the solution space to a few options. How do you pick one?&lt;&#x2F;p&gt;
&lt;p&gt;In a world where code is expensive, it&#x27;s preferrable to sort things out on a whiteboard. You analyze the different options, try to estimate
the pros and cons of each, then go out to build the one that comes out on top (or whose backer is the loudest in the room).&lt;br &#x2F;&gt;
With agents, you can &lt;strong&gt;prototype all options&lt;&#x2F;strong&gt;. Performance, complexity, memory consumption: you can &lt;strong&gt;measure them&lt;&#x2F;strong&gt;, thus injecting empirical data into the final decision.&lt;&#x2F;p&gt;
&lt;p&gt;I did this recently when working on a tree-based data structure. The server process forks, and the forked process must determine what needs to be garbage collected: how should a node be identified across the fork boundary? Using its memory address? Based on its position within the tree? Or is it better to introduce a notion of node index?&lt;br &#x2F;&gt;
I had agents build out all three approaches&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#verification&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;RediSearch&#x2F;RediSearch&#x2F;pull&#x2F;8276&quot;&gt;arena-based implementation&lt;&#x2F;a&gt; came out on top: simplest implementation (no &lt;code&gt;unsafe&lt;&#x2F;code&gt;), shortest GC pauses, and overall performance profile was satisfactory.&lt;&#x2F;p&gt;
&lt;p&gt;I wouldn&#x27;t have gone down that route if I had trusted my initial bias (position-based) and jumped straight to implementation.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;example-4-low-value-per-line-abstractions&quot;&gt;Example 4: Low value-per-line abstractions&lt;&#x2F;h1&gt;
&lt;p&gt;Some abstractions are useful, but tedious to write. I like to say that their &lt;strong&gt;value-per-line&lt;&#x2F;strong&gt; is low.&lt;&#x2F;p&gt;
&lt;p&gt;Take &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;RediSearch&#x2F;RediSearch&#x2F;pull&#x2F;8203&quot;&gt;this example&lt;&#x2F;a&gt;: ~2,000 lines of Rust code to create a safe interface over Redis&#x27; &lt;code&gt;RedisModule_Reply*&lt;&#x2F;code&gt; FFI functions. The builder minimizes &lt;code&gt;unsafe&lt;&#x2F;code&gt; in downstream code (i.e. just the constructor) and ensures that array and map lengths are correctly set.&lt;br &#x2F;&gt;
The code is extremely repetitive, no sane dev would enjoy hand-writing it; nor should they, the return on investment would be miserable. But it makes perfect sense to have an agent produce it! You have ruled out a whole class of minor mistakes with a modest token investment.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;example-5-pay-off-tech-debt-eagerly&quot;&gt;Example 5: Pay off tech debt eagerly&lt;&#x2F;h1&gt;
&lt;p&gt;Agentic coding works best with a closed feedback loop: the agent writes code, an automated check verifies it, and the agent iterates until the check passes. The tighter this loop, the more you can trust the output.&lt;&#x2F;p&gt;
&lt;p&gt;This reframes small tech debt items as &lt;strong&gt;investments in your verification infrastructure&lt;&#x2F;strong&gt;. Tightening the type constraints in a module, improving test coverage, enabling a new class of static analysis: these were always &quot;nice to have&quot;, but rarely worth pulling someone off other work. Agentic coding changes the equation: small clean-up tasks are easy to execute, easy to test and deliver obvious value.&lt;&#x2F;p&gt;
&lt;p&gt;Case in point: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;RediSearch&#x2F;RediSearch&#x2F;pull&#x2F;8372&quot;&gt;migrating small chunks of C code to Rust&lt;&#x2F;a&gt; to bring more code under the purview of Rust&#x27;s borrow-checker and &lt;code&gt;miri&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#miri&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. Each migration is a small task, easy to pull off for an agent. But the cumulative effect compounds: more code under static analysis provides a more reliable signal on whether changes are correct, thus speeding both agents and humans up.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h2&gt;
&lt;p&gt;These are just a few examples from my day-to-day work. I have no grand theory about where we&#x27;re heading as a profession, but I don&#x27;t think agentic coding spells the death of software engineering, nor of craft. If anything, it raises the bar on engineering discipline: organizations and systems where quality matters will invest more in verification, tooling, and feedback loops to extract real value from agentic workflows&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#oxide-and-friends&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is a great time to experiment, and get excited about what&#x27;s possible.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;&#x2F;h2&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;failures-are-artifacts&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;The Outcome Engineering manifesto calls this &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;o16g.com&#x2F;&quot;&gt;&quot;Failures are Artifacts&quot;&lt;&#x2F;a&gt;: &quot;Opinions are conjecture; outcomes are data. When an outcome fails, do not simply rollback. Dissect the failure. Understand why the hypothesis was wrong.&quot;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;verification&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;But how can you trust the generated prototypes? You must lay out the groundwork in advance! In this case, I had a comprehensive test suite in place, exercising the data structure exclusively via its public API, as well as end-to-end tests for the GC part. Correctness-wise, the verification loop was tight.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;miri&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;miri&quot;&gt;&lt;code&gt;miri&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a tool to catch undefined behaviour in Rust programs. It can&#x27;t reason about code that crosses the language barrier via FFI, which greatly limits its usefulness in mixed language codebases.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;oxide-and-friends&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;Bryan Cantrill, Adam Leventhal, Rain Paharia, and David Crespo discuss this at length in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;oxide-and-friends.transistor.fm&#x2F;episodes&#x2F;engineering-rigor-in-the-llm-age&quot;&gt;Engineering Rigor in the LLM Age&lt;&#x2F;a&gt; (Oxide and Friends).&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</description>
      </item>
      <item>
          <title>This month in Pavex, #10</title>
          <pubDate>Sun, 07 Apr 2024 08:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-10/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-10/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-10/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is a progress report about &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;, a new Rust web framework that I have been working on.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;style scoped&gt;
    &#x2F;* 0 to 299 *&#x2F;
    .pavex_logo {
        max-width: 100%;
        object-fit: scale-down;
        width: 1000px;
        aspect-ratio: 1 &#x2F; 1;
    }
    &#x2F;* 300 to X *&#x2F;
    @media (min-width: 500px) {
        .pavex_logo {
            max-width: 50%;
        }
    }
&lt;&#x2F;style&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-beta&#x2F;pavex_logo.png&quot; alt=&quot;Pavex&#x27;s logo&quot; class=&quot;pavex_logo&quot;&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;It&#x27;s time for another progress report on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;,
covering what has been done in March.&lt;br &#x2F;&gt;
In February, after releasing &lt;a href=&quot;&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, I thought adding cookie support
to Pavex would be a breeze.
A few hours of work on the integration, a couple more on the documentation, and we&#x27;d be done.
Boy, was I wrong!&lt;&#x2F;p&gt;
&lt;p&gt;It took me a whole month to get the cookie integration &lt;strong&gt;right&lt;&#x2F;strong&gt;—it has &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;cookies&#x2F;&quot;&gt;just been released&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
I had to add a couple of foundational features to our dependency injection engine to get something that felt &lt;strong&gt;idiomatic&lt;&#x2F;strong&gt;,
Rust-y: the ability to inject mutable references, new middleware types.&lt;br &#x2F;&gt;
That&#x27;s where the bulk of my time went this month—but it&#x27;s a good thing! Cookies were just the forcing function: I have
had those middleware designs in my head almost since the beginning of Pavex, and now they&#x27;re finally fully implemented.&lt;br &#x2F;&gt;
But enough with the pleasantries, let&#x27;s dive into the changes!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1by9crn&#x2F;this_month_in_pavex_10_new_middlewares_mutable&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;!-- TOC --&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#closed-beta&quot;&gt;Closed beta&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#one-size-doesn-t-fit-all-middleware-design&quot;&gt;One size doesn&#x27;t fit all: middleware design&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#downsides-of-wrapping-middlewares&quot;&gt;Downsides of wrapping middlewares&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#mutable-references-to-request-scoped-dependencies&quot;&gt;Mutable references to request-scoped dependencies&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#pre-processing-and-post-processing-middlewares&quot;&gt;Pre-processing and post-processing middlewares&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#mutable-references-revisited&quot;&gt;Mutable references, revisited&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#cookie-support&quot;&gt;Cookie support&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#connectioninfo&quot;&gt;&lt;code&gt;ConnectionInfo&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#rustnation-uk&quot;&gt;RustNation UK&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-10&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;!-- TOC --&gt;
&lt;h2 id=&quot;closed-beta&quot;&gt;Closed beta&lt;&#x2F;h2&gt;
&lt;p&gt;Invites to &lt;a href=&quot;&#x2F;posts&#x2F;pavex-is-in-closed-beta&quot;&gt;Pavex&#x27;s closed beta&lt;&#x2F;a&gt; keep going out!&lt;br &#x2F;&gt;
In March I sent out 102 new invites, and 88 of those joined Pavex&#x27;s Discord server to try out the framework.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re still waiting for an invite, don&#x27;t worry: it&#x27;s coming!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;one-size-doesn-t-fit-all-middleware-design&quot;&gt;One size doesn&#x27;t fit all: middleware design&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex has had support for middlewares almost since the beginning, in the form of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;middleware&#x2F;wrapping&#x2F;&quot;&gt;&lt;strong&gt;wrapping middlewares&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::middleware::Next;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::future::IntoFuture;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;middleware&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;C&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next&amp;lt;C&amp;gt;) -&amp;gt; Response
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;    C: IntoFuture&amp;lt;Output = Response&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Some pre-processing logic
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = next.await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Some post-processing logic
&lt;&#x2F;span&gt;&lt;span&gt;    response
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Wrapping middlewares lets you execute logic before and after the rest of the request processing pipeline.
They also give you access to the &lt;code&gt;Future&lt;&#x2F;code&gt; representing the rest of the request processing pipeline (via the &lt;code&gt;Next&lt;&#x2F;code&gt; type),
a prerequisite for attaching a &lt;code&gt;tracing::Span&lt;&#x2F;code&gt; to the request processing pipeline or enforcing timeouts.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s quite similar to the middleware interface you get in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;dev&#x2F;trait.Transform.html&quot;&gt;Actix Web&lt;&#x2F;a&gt;
or &lt;code&gt;axum&lt;&#x2F;code&gt; (via &lt;code&gt;tower::Service&lt;&#x2F;code&gt;).
You &quot;decorate&quot; an inner service, which in turn represents downstream middlewares or the request handler itself.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;downsides-of-wrapping-middlewares&quot;&gt;Downsides of wrapping middlewares&lt;&#x2F;h3&gt;
&lt;p&gt;In a way, wrapping middlewares are as powerful as you can get: you can do anything you want before and after the request handler,
and modify the request processing pipeline as you see fit.&lt;br &#x2F;&gt;
They have an issue, though: &lt;strong&gt;they don&#x27;t play nice with the borrow checker&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Every time you inject a reference as an input parameter to a wrapping middleware,
you are &lt;strong&gt;borrowing that type for the whole duration of the downstream request processing pipeline&lt;&#x2F;strong&gt;.
This can easily lead to borrow checker errors, especially if you are working with request-scoped dependencies.&lt;br &#x2F;&gt;
Let&#x27;s look at an example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::middleware::Next;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::future::IntoFuture;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;middleware&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;C&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next&amp;lt;C&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;my_type&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;MyType) -&amp;gt; Response
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;    C: IntoFuture&amp;lt;Output = Response&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Some pre-processing logic
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = next.await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Some post-processing logic
&lt;&#x2F;span&gt;&lt;span&gt;    response
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;MyType&lt;&#x2F;code&gt; is a request-scoped dependency: its constructor is invoked at most once for every incoming request.&lt;br &#x2F;&gt;
&lt;code&gt;middleware&lt;&#x2F;code&gt; is taking advantage of Pavex&#x27;s dependency injection engine to access a reference to &lt;code&gt;MyType&lt;&#x2F;code&gt;.
As a consequence, &lt;code&gt;MyType&lt;&#x2F;code&gt; is &lt;strong&gt;locked&lt;&#x2F;strong&gt; for the whole duration of the request processing pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;Suppose you want to work with &lt;code&gt;MyType&lt;&#x2F;code&gt; in your request handler, which is invoked by &lt;code&gt;next.await&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If the request handler takes &lt;code&gt;&amp;amp;mut MyType&lt;&#x2F;code&gt; as an input parameter, you&#x27;ll get an error: the immutable reference
to &lt;code&gt;MyType&lt;&#x2F;code&gt; borrowed by the wrapping middleware is still alive when the request handler is executed.&lt;&#x2F;li&gt;
&lt;li&gt;If the request handler takes &lt;code&gt;MyType&lt;&#x2F;code&gt; by value, Pavex is forced to clone the value to satisfy the borrow checker.
That&#x27;s inefficient. If &lt;code&gt;MyType&lt;&#x2F;code&gt; isn&#x27;t clonable, you&#x27;ll get an error.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The only scenario that doesn&#x27;t lead to borrow checker errors is the one where the request handler takes &lt;code&gt;&amp;amp;MyType&lt;&#x2F;code&gt; as an input parameter.
You can have as many immutable references to &lt;code&gt;MyType&lt;&#x2F;code&gt; as you want, so all is well.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mutable-references-to-request-scoped-dependencies&quot;&gt;Mutable references to request-scoped dependencies&lt;&#x2F;h3&gt;
&lt;p&gt;That&#x27;s an annoying issue since I want you to be able to work with mutable references.&lt;br &#x2F;&gt;
Going back to cookies, I want this to be possible:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::cookie::{ResponseCookie, ResponseCookies};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::time::{format_description::well_known::Iso8601, OffsetDateTime};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;response_cookies&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; ResponseCookies) -&amp;gt; Response {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; now = OffsetDateTime::now_utc().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;format&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;Iso8601::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;DEFAULT&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookie = ResponseCookie::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;last_visited&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, now).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;web&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    response_cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(cookie);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You need to add a new cookie to the response, therefore you get a &lt;strong&gt;mutable reference&lt;&#x2F;strong&gt; to &lt;code&gt;ResponseCookies&lt;&#x2F;code&gt;,
the collection where we are accumulating the cookies that must be sent back to the client.&lt;br &#x2F;&gt;
It feels very Rust-y: you want to mutate the collection, you get a mutable reference to it.&lt;&#x2F;p&gt;
&lt;p&gt;This wouldn&#x27;t work with a wrapping middleware, though.
We&#x27;d need a wrapping middleware to borrow (or consume) &lt;code&gt;ResponseCookies&lt;&#x2F;code&gt; in order to inject the &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; headers in the response.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::middleware::Next;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::cookie::{ResponseCookies, Processor};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::cookie::errors::InjectResponseCookiesError;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::future::IntoFuture;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The middleware to inject the cookie headers in the response.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;inject_response_cookies&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;C&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;response_cookies&lt;&#x2F;span&gt;&lt;span&gt;: ResponseCookies,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;processor&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;Processor,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Response, InjectResponseCookiesError&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;    C: IntoFuture&amp;lt;Output = Response&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; response = next.await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; value in response_cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;header_values&lt;&#x2F;span&gt;&lt;span&gt;(processor) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; value = HeaderValue::from_str(&amp;amp;value).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* ... *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)?;
&lt;&#x2F;span&gt;&lt;span&gt;        response = response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;append_header&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;SET_COOKIE&lt;&#x2F;span&gt;&lt;span&gt;, value);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This design would prevent us from working with mutable references to &lt;code&gt;ResponseCookies&lt;&#x2F;code&gt; in the request handler.&lt;&#x2F;p&gt;
&lt;p&gt;One possible solution to this dilemma would be to leverage interior mutability for &lt;code&gt;ResponseCookies&lt;&#x2F;code&gt;—make it a
&lt;code&gt;Rc&amp;lt;RefCell&amp;lt;..&amp;gt;&amp;gt;&lt;&#x2F;code&gt;, thus allowing you to mutate the collection even if you only have an immutable reference to it.&lt;br &#x2F;&gt;
That can work, but it&#x27;s not ideal: you lose the ability to leverage the borrow checker to catch bugs at compile time,
and it feels like incidental complexity.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pre-processing-and-post-processing-middlewares&quot;&gt;Pre-processing and post-processing middlewares&lt;&#x2F;h3&gt;
&lt;p&gt;The real issue is that we are &lt;strong&gt;over-borrowing&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
In &lt;code&gt;inject_response_cookies&lt;&#x2F;code&gt; we don&#x27;t need to borrow &lt;code&gt;ResponseCookies&lt;&#x2F;code&gt; for the whole duration of the request processing pipeline.
We only need to borrow&#x2F;consume &lt;code&gt;ResponseCookies&lt;&#x2F;code&gt; &lt;em&gt;after&lt;&#x2F;em&gt; the request handler has been executed,
to inject the &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; headers in the response.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s why I&#x27;ve introduced a new kind of middleware: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;middleware&#x2F;pre_processing&#x2F;&quot;&gt;&lt;strong&gt;pre-processing&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;
and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;middleware&#x2F;post_processing&#x2F;&quot;&gt;&lt;strong&gt;post-processing&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; middlewares.&lt;br &#x2F;&gt;
A post-processing middleware for cookies, for example, looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::cookie::{ResponseCookies, Processor};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::cookie::errors::InjectResponseCookiesError;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The middleware to inject the cookie headers in the response.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;inject_response_cookies&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;C&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;response&lt;&#x2F;span&gt;&lt;span&gt;: Response,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;response_cookies&lt;&#x2F;span&gt;&lt;span&gt;: ResponseCookies,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;processor&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;Processor,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Response, InjectResponseCookiesError&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; value in response_cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;header_values&lt;&#x2F;span&gt;&lt;span&gt;(processor) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; value = HeaderValue::from_str(&amp;amp;value).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* ... *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)?;
&lt;&#x2F;span&gt;&lt;span&gt;        response = response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;append_header&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;SET_COOKIE&lt;&#x2F;span&gt;&lt;span&gt;, value);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You get a &lt;code&gt;Response&lt;&#x2F;code&gt; as an input parameter, and you return a &lt;code&gt;Response&lt;&#x2F;code&gt; (or &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;middleware&#x2F;post_processing&#x2F;#middlewares-can-fail&quot;&gt;a &lt;code&gt;Result&amp;lt;Response, _&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;).
As all components in Pavex,
it can also take advantage of the dependency injection engine to access the dependencies it needs.&lt;br &#x2F;&gt;
This is not a toy example either: the cookie injector middleware in Pavex is implemented &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;6920f2e2c47001aa38639baf4a53dc06da85d721&#x2F;libs&#x2F;pavex&#x2F;src&#x2F;cookie&#x2F;components.rs#L40&quot;&gt;&lt;strong&gt;exactly like this&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Pre-processing middlewares, on the other hand, are executed before the request handler is invoked.
They can be used to short-circuit the request processing pipeline—e.g. to enforce some kind of access control
or rate limiting.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::http::{HeaderValue, header::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;LOCATION&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::middleware::Processing;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::request::RequestHead;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; If the request path ends with a `&#x2F;`,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; redirect to the same path without the trailing `&#x2F;`.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;redirect_to_normalized&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request_head&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RequestHead) -&amp;gt; Processing
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;Some(normalized_path) = request_head.target.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;strip_suffix&lt;&#x2F;span&gt;&lt;span&gt;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; No need to redirect, we continue processing the request.
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Processing::Continue;
&lt;&#x2F;span&gt;&lt;span&gt;   };
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; location = HeaderValue::from_str(normalized_path).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; redirect = Response::temporary_redirect().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;insert_header&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;LOCATION&lt;&#x2F;span&gt;&lt;span&gt;, location);
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Short-circuit the request processing pipeline and return the redirect response
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; to the client without invoking downstream middlewares and the request handler.
&lt;&#x2F;span&gt;&lt;span&gt;   Processing::EarlyReturn(redirect)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pre-processing middlewares are expected to return a &lt;code&gt;Processing&lt;&#x2F;code&gt; instance,
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;middleware&#x2F;pre_processing&#x2F;#middlewares-can-fail&quot;&gt;or a &lt;code&gt;Result&amp;lt;Processing, _&amp;gt;&lt;&#x2F;code&gt; if they are fallible&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Pre-processing and post-processing middlewares look &lt;em&gt;less&lt;&#x2F;em&gt; powerful than wrapping middlewares, but they offer a
simpler interface (e.g. no need to deal with &lt;code&gt;Next&amp;lt;C&amp;gt;&lt;&#x2F;code&gt;) and let you scope borrows to the
portion of the request processing pipeline where they are strictly needed.&lt;&#x2F;p&gt;
&lt;p&gt;They complete Pavex&#x27;s middleware story, as I had originally envisioned it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mutable-references-revisited&quot;&gt;Mutable references, revisited&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve been talking about mutable references for a few sections, but up until now they weren&#x27;t allowed in Pavex!
The dependency injection engine would only let you inject immutable references to dependencies or
consume them by value.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s no longer the case: &lt;strong&gt;you can now inject mutable references&lt;&#x2F;strong&gt; to dependencies in your request handlers,
pre-processing, and post-processing middlewares.&lt;br &#x2F;&gt;
They remain forbidden in constructors, since &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;dependency_injection&#x2F;core_concepts&#x2F;constructors&#x2F;#no-mutations&quot;&gt;their invocation order is not guaranteed&lt;&#x2F;a&gt;,
and in wrapping middlewares, for the reasons we&#x27;ve discussed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cookie-support&quot;&gt;Cookie support&lt;&#x2F;h2&gt;
&lt;p&gt;Thanks to all this preparatory work (pre- and post-processing middlewares, support for mutable references),
I was able to release a nice cookie integration in Pavex.&lt;br &#x2F;&gt;
You can find all the details in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;cookies&#x2F;&quot;&gt;Pavex&#x27;s documentation&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::cookie::{RemovalCookie, ResponseCookies};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;response_cookies&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; ResponseCookies) -&amp;gt; Response {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookie = RemovalCookie::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;last_visited&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;web&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    response_cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(cookie);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Further processing...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;connectioninfo&quot;&gt;&lt;code&gt;ConnectionInfo&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex has added a new &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;dependency_injection&#x2F;core_concepts&#x2F;framework_primitives&#x2F;&quot;&gt;framework primitive&lt;&#x2F;a&gt;:
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;request_data&#x2F;connection_info&#x2F;&quot;&gt;&lt;code&gt;ConnectionInfo&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::connection::ConnectionInfo;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::Response;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;conn&lt;&#x2F;span&gt;&lt;span&gt;: ConnectionInfo) -&amp;gt; Response {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; addr = conn.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;peer_addr&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    Response::ok().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_typed_body&lt;&#x2F;span&gt;&lt;span&gt;(format!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Your address is &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{addr}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can use it to retrieve the peer address of the client that established the connection to the server,
an information that&#x27;s often logged.&lt;br &#x2F;&gt;
More importantly, it&#x27;s the first large contribution by another developer to Pavex—up until now,
I&#x27;ve been the only one working on the framework.
Thanks &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hlbarber&quot;&gt;Harry&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rustnation-uk&quot;&gt;RustNation UK&lt;&#x2F;h2&gt;
&lt;p&gt;Before Easter I had the pleasure to give a talk at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rustnationuk.com&#x2F;&quot;&gt;RustNation UK&lt;&#x2F;a&gt; on Pavex:
why I started working on it, where it&#x27;s going, and the rationale behind some of the design decisions I made.&lt;&#x2F;p&gt;
&lt;p&gt;The conference was incredibly fast and the recording of the talk is already available on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=cMea6IMRk2s&amp;amp;t&quot;&gt;YouTube&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;div align=&quot;center&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;cMea6IMRk2s?si=-KACqX8jcz1tAlW3&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I&#x27;m quite happy with how it came out, especially the live coding part. Check it out!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h2&gt;
&lt;p&gt;I want to launch Pavex&#x27;s open beta before the summer.&lt;br &#x2F;&gt;
I&#x27;m also a firm believer in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Eating_your_own_dog_food&quot;&gt;dogfooding&lt;&#x2F;a&gt;—I want to use
Pavex to build Pavex&#x27;s admin panel, and there are still a few features missing to make that possible.&lt;br &#x2F;&gt;
In April I plan to close those gaps and, if all goes well, have a rudimentary version of the admin panel up and running.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s all for this month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1by9crn&#x2F;this_month_in_pavex_10_new_middlewares_mutable&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2024-04-07T08:00:10.47Z&quot;&gt;April 2024&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;this-month-in-pavex-09&amp;#x2F;&quot;&gt;This month in Pavex, #9&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Coming Soon&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>biscotti, a new crate for HTTP cookies</title>
          <pubDate>Sat, 09 Mar 2024 08:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/biscotti-http-cookies-in-rust/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/biscotti-http-cookies-in-rust/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/biscotti-http-cookies-in-rust/">&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; (&quot;cookies&quot;, but in Italian) is a new Rust crate to
handle HTTP cookies on the server side.&lt;br &#x2F;&gt;
&lt;code&gt;biscotti&lt;&#x2F;code&gt;&#x27;s API strives to be as faithful as possible to the underlying semantics of HTTP cookies, with a keen eye for
edge cases and security:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Separate types for request and response cookies&lt;&#x2F;li&gt;
&lt;li&gt;Support for working with multiple cookies with the same name, in both requests and responses&lt;&#x2F;li&gt;
&lt;li&gt;Centralized management of cookie&#x27;s cryptographic guarantees (i.e. what gets signed or encrypted)&lt;&#x2F;li&gt;
&lt;li&gt;Built-in support for rotating signing&#x2F;encryption keys over time&lt;&#x2F;li&gt;
&lt;li&gt;Percent-encoding&#x2F;decoding cookies enabled by default (but you can opt out)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The first release of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is available on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;biscotti&quot;&gt;crates.io&lt;&#x2F;a&gt; and its documentation
is hosted on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&quot;&gt;docs.rs&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
The rest of this article will cover the motivation behind &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and show where it differs (and why) from the
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate, the de-facto standard for cookies in Rust.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this article on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1bb7vtm&#x2F;biscotti_a_new_crate_for_http_cookies&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;!-- TOC --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookies-in-rust&quot;&gt;Cookies in Rust&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#why-biscotti&quot;&gt;Why &lt;code&gt;biscotti&lt;&#x2F;code&gt;?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#design-goals&quot;&gt;Design goals&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookies-crash-course&quot;&gt;Cookies crash course&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#request-and-response-cookies-are-different&quot;&gt;Request and response cookies are different&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookie&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookie-collections&quot;&gt;Cookie collections&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#request-cookies&quot;&gt;Request cookies&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookie-1&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#biscotti-1&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#response-cookies&quot;&gt;Response cookies&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookie-2&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#biscotti-2&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#signed-and-encrypted-cookies&quot;&gt;Signed and encrypted cookies&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#cookie-3&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#biscotti-3&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;biscotti-http-cookies-in-rust&#x2F;#conclusion&quot;&gt;Conclusion&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;!-- TOC --&gt;
&lt;h2 id=&quot;cookies-in-rust&quot;&gt;Cookies in Rust&lt;&#x2F;h2&gt;
&lt;p&gt;The entirety of the Rust web ecosystem has effectively standardized on one crate for HTTP cookies:
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
It was created &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&#x2F;commit&#x2F;131f61c9a38df3533d30de4d7b1f6629d9b2c399&quot;&gt;almost 10 years ago&lt;&#x2F;a&gt;
by Alex Crichton, and it has been taken over and maintained by Sergio Benitez
(author and maintainer of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rocket.rs&quot;&gt;Rocket&lt;&#x2F;a&gt;) for the past 7 years.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is re-exported by every major web framework:
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;rocket&#x2F;0.5.0&#x2F;rocket&#x2F;http&#x2F;struct.Cookie.html&quot;&gt;Rocket&lt;&#x2F;a&gt; (obviously),
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;cookie&#x2F;struct.Cookie.html&quot;&gt;Actix Web&lt;&#x2F;a&gt;,
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum-extra&#x2F;0.9.2&#x2F;axum_extra&#x2F;extract&#x2F;cookie&#x2F;index.html&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;,
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;poem&#x2F;latest&#x2F;poem&#x2F;web&#x2F;cookie&#x2F;index.html&quot;&gt;&lt;code&gt;poem&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and so on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-biscotti&quot;&gt;Why &lt;code&gt;biscotti&lt;&#x2F;code&gt;?&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m working on a new web framework, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;, and I was about to do the same:
grab the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate, re-export it as a &lt;code&gt;pavex::cookie&lt;&#x2F;code&gt; module and call it a day.&lt;br &#x2F;&gt;
Then I paused: in the past I had to fight the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate to handle more &quot;advanced&quot; cookie scenarios.
Could I craft an API simple enough for the 80% of the use cases, but powerful enough to cover the remaining 20%?&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s how &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; was born!&lt;br &#x2F;&gt;
It started as a fork of the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate, but the API has diverged significantly since I got started.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;design-goals&quot;&gt;Design goals&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a general-purpose crate: it can be used to handle cookies both on the server and on the client side.&lt;br &#x2F;&gt;
That&#x27;s not the case for &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. I deliberately focused &lt;strong&gt;on the server side&lt;&#x2F;strong&gt;.
There is no support, for example, for parsing &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; headers on the client side in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cookies-crash-course&quot;&gt;Cookies crash course&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s start with a quick refresher on how cookies work.&lt;br &#x2F;&gt;
Cookies are a way to attach &lt;strong&gt;state&lt;&#x2F;strong&gt; to an otherwise stateless protocol, HTTP.
The state is stored on the client side, usually by a browser.&lt;br &#x2F;&gt;
The state is passed back and forth between the client and the server using the &lt;code&gt;Cookie&lt;&#x2F;code&gt; and &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; headers.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt; is used by clients to send &lt;strong&gt;relevant&lt;&#x2F;strong&gt; cookies to the server when they issue requests.&lt;br &#x2F;&gt;
The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt;, instead, is used by the server to alter the state on the client-side, either by
creating new cookies, removing existing ones, or updating their attributes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;request-and-response-cookies-are-different&quot;&gt;Request and response cookies are different&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;code&gt;Cookie&lt;&#x2F;code&gt; and &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; headers have very different semantics:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt; is a comma-separated list of &lt;code&gt;name=value&lt;&#x2F;code&gt; pairs&lt;&#x2F;li&gt;
&lt;li&gt;Each &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt;, instead, can specify a lot of additional attributes on top of the &lt;code&gt;name=value&lt;&#x2F;code&gt; pair.
You have &lt;code&gt;Path&lt;&#x2F;code&gt;, &lt;code&gt;Domain&lt;&#x2F;code&gt;, &lt;code&gt;Expires&lt;&#x2F;code&gt;, &lt;code&gt;Max-Age&lt;&#x2F;code&gt;, &lt;code&gt;Secure&lt;&#x2F;code&gt;, &lt;code&gt;HttpOnly&lt;&#x2F;code&gt;, &lt;code&gt;SameSite&lt;&#x2F;code&gt;, etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;cookie&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;You don&#x27;t see this distinction in the API of the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate.
The same &lt;code&gt;Cookie&lt;&#x2F;code&gt; type is used to represent both incoming and outgoing cookies.&lt;br &#x2F;&gt;
Incoming cookies are &lt;code&gt;Cookie&lt;&#x2F;code&gt;s with all optional attributes set to &lt;code&gt;None&lt;&#x2F;code&gt;.
It is us up to the user to remember that this doesn&#x27;t imply that,
in the cookie jar on the client side, those attributes are actually unset!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, I chose to encode the distinction in the type system:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.RequestCookie.html&quot;&gt;&lt;code&gt;RequestCookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; represents a cookie parsed from a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt;. It has a &lt;code&gt;name&lt;&#x2F;code&gt; and a &lt;code&gt;value&lt;&#x2F;code&gt;, nothing more.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.ResponseCookie.html&quot;&gt;&lt;code&gt;ResponseCookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, instead, represents a cookie that you want to send to the client via a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt;.
It has a &lt;code&gt;name&lt;&#x2F;code&gt;, a &lt;code&gt;value&lt;&#x2F;code&gt; and it allows you to set all those additional attributes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can&#x27;t mix them up—you can&#x27;t accidentally send a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.RequestCookie.html&quot;&gt;&lt;code&gt;RequestCookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to the client,
or try to set a &lt;code&gt;Path&lt;&#x2F;code&gt; attribute on a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.RequestCookie.html&quot;&gt;&lt;code&gt;RequestCookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cookie-collections&quot;&gt;Cookie collections&lt;&#x2F;h2&gt;
&lt;p&gt;The differences between request and response cookies don&#x27;t stop there. They also have different semantics when
it comes to &lt;strong&gt;cookie collections&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;request-cookies&quot;&gt;Request cookies&lt;&#x2F;h2&gt;
&lt;p&gt;In the &lt;code&gt;Cookie&lt;&#x2F;code&gt; header, you can have multiple cookies with the same name.&lt;br &#x2F;&gt;
Consider the following well-formed &lt;code&gt;GET&lt;&#x2F;code&gt; request:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;http&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-http &quot;&gt;&lt;code class=&quot;language-http&quot; data-lang=&quot;http&quot;&gt;&lt;span&gt;GET &#x2F;home HTTP&#x2F;1.1
&lt;&#x2F;span&gt;&lt;span&gt;Host: example.com
&lt;&#x2F;span&gt;&lt;span&gt;Cookie: name=first; name=second;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This may happen, for example, if the two &lt;code&gt;name&lt;&#x2F;code&gt; cookies were set with a different &lt;code&gt;Path&lt;&#x2F;code&gt; attributes
and both paths are relevant for the current request (e.g. &lt;code&gt;&#x2F;&lt;&#x2F;code&gt; and &lt;code&gt;&#x2F;home&lt;&#x2F;code&gt; in our example above).&lt;br &#x2F;&gt;
In this scenario, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;a&#x2F;24214538&quot;&gt;browsers will send both cookies, even if they have the same name&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Depending on what you are trying to do, it might make sense to access the first cookie value, the second cookie value, or all of them!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cookie-1&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate gives you a &lt;code&gt;CookieJar&lt;&#x2F;code&gt; type to work with cookie collections, regardless of whether they are
request or response cookies.&lt;br &#x2F;&gt;
For the purpose of this example, you can think of &lt;code&gt;CookieJar&lt;&#x2F;code&gt; as a &lt;code&gt;HashMap&amp;lt;String, Cookie&amp;gt;&lt;&#x2F;code&gt;.
If you insert two cookies with the same name, the second one will overwrite the first one.&lt;&#x2F;p&gt;
&lt;p&gt;Most web frameworks follow this algorithm to parse the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Set-Cookie&quot;&gt;&lt;code&gt;Cookie&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt; with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;cookie::{Cookie, CookieJar};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookie_header = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name=first; name=second;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; jar = CookieJar::new();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; cookie in Cookie::split_parse(cookie_header) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookie = cookie.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add_original&lt;&#x2F;span&gt;&lt;span&gt;(cookie);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In our example above, the &lt;code&gt;jar&lt;&#x2F;code&gt; would only contain the last &lt;code&gt;name&lt;&#x2F;code&gt; cookie, with the value &lt;code&gt;second&lt;&#x2F;code&gt;. You can try this out
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;?version=stable&amp;amp;mode=debug&amp;amp;edition=2021&amp;amp;gist=cd85c8993d47f65223149090c0ebf381&quot;&gt;Rust playground&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
You can find this very code snippet, with minor variations, in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tokio-rs&#x2F;axum&#x2F;blob&#x2F;b6b203b3065e4005bda01efac8429176da055ae2&#x2F;axum-extra&#x2F;src&#x2F;extract&#x2F;cookie&#x2F;mod.rs#L124&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;poem-web&#x2F;poem&#x2F;blob&#x2F;e32e44953f5d87a497728370891c147fb30b2733&#x2F;poem&#x2F;src&#x2F;web&#x2F;cookie.rs#L515&quot;&gt;&lt;code&gt;poem&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
They&#x27;re not to blame: &lt;code&gt;CookieJar&lt;&#x2F;code&gt; isn&#x27;t designed to handle multiple cookies with the same name,
you can&#x27;t &quot;fix it&quot; without abandoning &lt;code&gt;CookieJar&lt;&#x2F;code&gt; altogether.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;biscotti-1&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; takes a different approach. We have a dedicated type, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.RequestCookies.html&quot;&gt;&lt;code&gt;RequestCookies&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, to handle incoming cookies as
a collection.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;biscotti::{Processor, config::Config, RequestCookies};
&lt;&#x2F;span&gt;&lt;span&gt;  
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; header = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name=first; name=second&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; processor: Processor = Config::default().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookies = RequestCookies::parse_header(header, &amp;amp;processor).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.RequestCookies.html&quot;&gt;&lt;code&gt;RequestCookies&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a multivalued map, like &lt;code&gt;HeaderMap&lt;&#x2F;code&gt; in &lt;code&gt;http&lt;&#x2F;code&gt;: you can access the first cookie with a given name,
or opt to fetch all values associated with that cookie name.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; value = cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!(value, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;first&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; With `get_all`, you get an iterator over all the cookies with that name.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; all_values: Vec&amp;lt;_&amp;gt; = cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_all&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;values&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;collect&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!(all_values, vec![&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;first&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;second&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The 80% of the time you can get by with treating &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.RequestCookies.html&quot;&gt;&lt;code&gt;RequestCookies&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; as a &lt;code&gt;HashMap&amp;lt;String, RequestCookie&amp;gt;&lt;&#x2F;code&gt;, but
you have the flexibility to handle the remaining 20% of the cases when you need to.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;response-cookies&quot;&gt;Response cookies&lt;&#x2F;h2&gt;
&lt;p&gt;A similar issue arises when working with response cookies.&lt;br &#x2F;&gt;
The following response is well-formed:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;http&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-http &quot;&gt;&lt;code class=&quot;language-http&quot; data-lang=&quot;http&quot;&gt;&lt;span&gt;HTTP&#x2F;1.1 200 OK
&lt;&#x2F;span&gt;&lt;span&gt;Set-Cookie: name=; Path=&#x2F;; Expires=Thu, 01 Jan 1970 00:00:00 GMT
&lt;&#x2F;span&gt;&lt;span&gt;Set-Cookie: name=value; Path=&#x2F;home; 
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The server is instructing the client to manipulate two cookies with the same name, but different path attributes.&lt;br &#x2F;&gt;
In particular:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The first &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header is telling the client to remove the existing &lt;code&gt;name&lt;&#x2F;code&gt; cookie with &lt;code&gt;Path=&#x2F;&lt;&#x2F;code&gt; attribute,
using an &lt;code&gt;Expires&lt;&#x2F;code&gt; attribute set to the past.&lt;&#x2F;li&gt;
&lt;li&gt;The second &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header is telling the client to create a new &lt;code&gt;name&lt;&#x2F;code&gt; cookie set to &lt;code&gt;value&lt;&#x2F;code&gt; with &lt;code&gt;Path=&#x2F;home&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This is spec-compliant, and all major browsers will behave as expected here.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cookie-2&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;With the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate you need to use &lt;code&gt;CookieJar&lt;&#x2F;code&gt; to handle response cookies as well.&lt;br &#x2F;&gt;
The same semantic issues we saw with request cookies apply here as well:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;cookie::{CookieBuilder, CookieJar};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; jar = CookieJar::new();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; removal_cookie = CookieBuilder::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;finish&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;removal_cookie.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;make_removal&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add&lt;&#x2F;span&gt;&lt;span&gt;(removal_cookie);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; new_cookie = CookieBuilder::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;finish&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add&lt;&#x2F;span&gt;&lt;span&gt;(new_cookie);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this case, &lt;code&gt;jar&lt;&#x2F;code&gt; will contain a single &lt;code&gt;name&lt;&#x2F;code&gt; cookie, set to &lt;code&gt;value&lt;&#x2F;code&gt; with &lt;code&gt;Path=&#x2F;home&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;assert_eq!(jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;delta&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span&gt;(), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; set_cookie = jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;delta&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!(set_cookie.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;(), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name=value; Path=&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The removal cookie is lost and won&#x27;t be sent to the client (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;play.rust-lang.org&#x2F;?version=stable&amp;amp;mode=debug&amp;amp;edition=2021&amp;amp;gist=34de550745c98335ffa0bd498a3539e6&quot;&gt;Playground link&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;biscotti-2&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; has a dedicated type, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.ResponseCookies.html&quot;&gt;&lt;code&gt;ResponseCookies&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, to handle outgoing cookies as a collection.&lt;br &#x2F;&gt;
You can picture it as a &lt;code&gt;HashMap&amp;lt;ResponseCookieId, ResponseCookie&amp;gt;&lt;&#x2F;code&gt;, where &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.ResponseCookieId.html&quot;&gt;&lt;code&gt;ResponseCookieId&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; captures:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The name of the cookie&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;code&gt;Path&lt;&#x2F;code&gt; attribute of the cookie&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;code&gt;Domain&lt;&#x2F;code&gt; attribute of the cookie&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As long as the combination of those three values is different, response cookies will be considered to be distinct.&lt;&#x2F;p&gt;
&lt;p&gt;Going back to our example, in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; you would do:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;biscotti::{ResponseCookies, RemovalCookie, ResponseCookie};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; cookies = ResponseCookies::new();
&lt;&#x2F;span&gt;&lt;span&gt;cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(RemovalCookie::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;));
&lt;&#x2F;span&gt;&lt;span&gt;cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt;(ResponseCookie::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;));
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can then obtain the respective &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header values:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;maplit::hashset;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::collections::HashSet;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;biscotti::{Processor, config::Config};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; processor: Processor = Config::default().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; header_values: HashSet&amp;lt;_&amp;gt; = cookies.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;header_values&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;processor).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;collect&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;assert_eq!(header_values, hashset! {
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name=; Path=&#x2F;; Expires=Thu, 01 Jan 1970 00:00:00 GMT&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name=value; Path=&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Both &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header values are present, as we wanted.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;signed-and-encrypted-cookies&quot;&gt;Signed and encrypted cookies&lt;&#x2F;h2&gt;
&lt;p&gt;Both &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; provide built-in support for signing and encrypting cookies.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cookie-3&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, you opt into signing or encrypting cookies by creating a &quot;child&quot; jar from the main &lt;code&gt;CookieJar&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;cookie::{Cookie, CookieJar, Key, Private};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; key = Key::generate()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; jar = CookieJar::new();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; child_jar = jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;private&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;key);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; This cookie will be encrypted!
&lt;&#x2F;span&gt;&lt;span&gt;child_jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add&lt;&#x2F;span&gt;&lt;span&gt;(Cookie::new(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;));
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The same process is used to verify and decrypt cookies:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; jar = CookieJar::new();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; child_jar = jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;private&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;key);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; This cookie will be decrypted! 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookie = child_jar.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;biscotti-3&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, signing and encryption are managed &lt;strong&gt;centrally&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
You populate a &lt;strong&gt;single&lt;&#x2F;strong&gt; &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;config&#x2F;struct.Config.html&quot;&gt;&lt;code&gt;Config&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; with the cryptographic setup you want to use, and then you convert it into a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.Processor.html&quot;&gt;&lt;code&gt;Processor&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;biscotti::{Processor, Key, config::{Config, CryptoType}};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; config = Config::default();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; rule = CryptoRule {
&lt;&#x2F;span&gt;&lt;span&gt;    cookie_names: vec![&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;session&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()],
&lt;&#x2F;span&gt;&lt;span&gt;    r#&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;: CryptoType::Signing,
&lt;&#x2F;span&gt;&lt;span&gt;    key: Key::generate(),
&lt;&#x2F;span&gt;&lt;span&gt;    secondary_keys: vec![],
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;config.crypto_rules.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(rule);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; processor: Processor = config.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.Processor.html&quot;&gt;&lt;code&gt;Processor&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a required argument when parsing request cookies or when generating the &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt; header values for response cookies,
as you&#x27;ve seen in the code snippets from the previous sections.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;biscotti::{Processor, config::Config, RequestCookies};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; header = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;session=unsigned-value&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `parse_header` will return an error since `session` is expected to be signed
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; and it isn&amp;#39;t in this case.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cookies = RequestCookies::parse_header(header, &amp;amp;processor).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The rest of the application doesn&#x27;t need to know about the cryptographic setup, nor does it need access
to the cryptographic keys.&lt;&#x2F;p&gt;
&lt;p&gt;In addition to signing and encryption, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; also provides built-in support for rotating signing&#x2F;encryption keys over time
via the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;config&#x2F;struct.CryptoRule.html#structfield.secondary_keys&quot;&gt;&lt;code&gt;secondary_keys&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; field in &lt;code&gt;CryptoRule&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
Cookies will always be signed&#x2F;encrypted with the primary key, but the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;biscotti&#x2F;0.1.1&#x2F;biscotti&#x2F;struct.Processor.html&quot;&gt;&lt;code&gt;Processor&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; will be also accept cookies that were
signed&#x2F;encrypted with any of the secondary keys.
This allows you to rotate keys over time without invalidating all existing cookies.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, just like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, is a framework-agnostic library.&lt;br &#x2F;&gt;
If you are building a web framework, you can use &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; to handle cookies in your HTTP server.
If you need &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&#x27;s features in your &lt;code&gt;axum&lt;&#x2F;code&gt; or &lt;code&gt;poem&lt;&#x2F;code&gt; application, you should be able to integrate it seamlessly.&lt;&#x2F;p&gt;
&lt;p&gt;Keep in mind that &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a new crate—it isn&#x27;t as battle-tested as &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SergioBenitez&#x2F;cookie-rs&quot;&gt;&lt;code&gt;cookie&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and it might have some rough edges.
Feel free to open an issue on the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;GitHub repository&lt;&#x2F;a&gt;
if you find any bugs or if you have any suggestions.&lt;&#x2F;p&gt;
&lt;p&gt;I hope you find &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;biscotti&quot;&gt;&lt;code&gt;biscotti&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; useful!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this article on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1bb7vtm&#x2F;biscotti_a_new_crate_for_http_cookies&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
</description>
      </item>
      <item>
          <title>This month in Pavex, #9</title>
          <pubDate>Sun, 25 Feb 2024 08:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-09/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-09/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-09/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is a progress report about &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;, a new Rust web framework that I have been working on.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;style scoped&gt;
    &#x2F;* 0 to 299 *&#x2F;
    .pavex_logo {
        max-width: 100%;
        object-fit: scale-down;
        width: 1000px;
        aspect-ratio: 1 &#x2F; 1;
    }
    &#x2F;* 300 to X *&#x2F;
    @media (min-width: 500px) {
        .pavex_logo {
            max-width: 50%;
        }
    }
&lt;&#x2F;style&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-beta&#x2F;pavex_logo.png&quot; alt=&quot;Pavex&#x27;s logo&quot; class=&quot;pavex_logo&quot;&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;It&#x27;s time for another progress report on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;,
covering what has been done in January and February!&lt;br &#x2F;&gt;
There&#x27;s &lt;em&gt;a lot&lt;&#x2F;em&gt; going on this time, across multiple domains: user experience, functionality, bug fixes.
Easier to dive straight into the changes!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1b0k3y3&#x2F;this_month_in_pavex_9_rust_web_framework&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;!-- TOC --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#closed-beta&quot;&gt;Closed beta&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#documentation&quot;&gt;Documentation&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#version-management&quot;&gt;Version management&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#kits&quot;&gt;Kits&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#pavex-tracing&quot;&gt;&lt;code&gt;pavex_tracing&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#error-observers&quot;&gt;Error observers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#terser-registrations&quot;&gt;Terser registrations&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#ci&quot;&gt;CI&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#windows&quot;&gt;Windows&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#miscellaneous&quot;&gt;Miscellaneous&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-09&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;!-- TOC --&gt;
&lt;h2 id=&quot;closed-beta&quot;&gt;Closed beta&lt;&#x2F;h2&gt;
&lt;p&gt;At the beginning of January I started to send out invites to Pavex&#x27;s &lt;a href=&quot;&#x2F;posts&#x2F;pavex-is-in-closed-beta&quot;&gt;closed beta&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
There was a lot more excitement for Pavex than I was expecting: 479 people joined the waiting list!&lt;br &#x2F;&gt;
To keep things manageable, I sent invites out in waves: once every ~10 days, 30 invites at a time.
I&#x27;m almost halfway through the list: 183 invites have gone out in January and February, and 147 of those joined
Pavex&#x27;s Discord server.&lt;&#x2F;p&gt;
&lt;p&gt;The more the framework stabilizes, the faster I&#x27;ll be sending invites out. If you&#x27;re still waiting, thank you for your patience!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;&#x2F;h2&gt;
&lt;p&gt;Documentation is never finished, but after &lt;a href=&quot;&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;&quot;&gt;a big push&lt;&#x2F;a&gt; it&#x27;s now in a pretty good spot.
It&#x27;s also &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;&quot;&gt;publicly available on Pavex&#x27;s website&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;version-management&quot;&gt;Version management&lt;&#x2F;h2&gt;
&lt;p&gt;When working on a Pavex project, you rely on both &lt;code&gt;pavex&lt;&#x2F;code&gt; (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;pavex_cli&quot;&gt;the CLI&lt;&#x2F;a&gt;)
and &lt;code&gt;pavex&lt;&#x2F;code&gt; (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;pavex&quot;&gt;the library crate&lt;&#x2F;a&gt;).&lt;br &#x2F;&gt;
Weird stuff may happen if you use different versions in the same project.
Cargo doesn&#x27;t make it easy either: libraries are naturally scoped to a project, CLIs are global.&lt;&#x2F;p&gt;
&lt;p&gt;To solve the issue once and for all, I&#x27;ve re-architected Pavex&#x27;s CLI. &lt;code&gt;pavex&lt;&#x2F;code&gt; is now a &lt;strong&gt;version manager&lt;&#x2F;strong&gt;,
just like &lt;code&gt;rustup&lt;&#x2F;code&gt; for Rust&#x27;s toolchain.&lt;br &#x2F;&gt;
The &quot;true&quot; compiler logic has been moved to a different CLI, &lt;code&gt;pavexc&lt;&#x2F;code&gt;.
When you invoke a Pavex command from inside a project, &lt;code&gt;pavex&lt;&#x2F;code&gt; will automatically determine which version of &lt;code&gt;pavexc&lt;&#x2F;code&gt;
needs to be used by looking at the version of &lt;code&gt;pavex&lt;&#x2F;code&gt; in your &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt;. You never interact with &lt;code&gt;pavexc&lt;&#x2F;code&gt;
directly (unless you want to), just like you never deal with &lt;code&gt;rustc&lt;&#x2F;code&gt; in your day-to-day Rust work.&lt;&#x2F;p&gt;
&lt;p&gt;There have been no reports of issues related to version management ever since this was released. Success!&lt;&#x2F;p&gt;
&lt;p&gt;As a bonus, I also built:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;a command to uninstall Pavex and clear its cache data (&lt;code&gt;pavex self uninstall&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;a command to update to the latest released version of the CLI (&lt;code&gt;pavex self update&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;kits&quot;&gt;Kits&lt;&#x2F;h2&gt;
&lt;p&gt;I introduced a new concept: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;dependency_injection&#x2F;core_concepts&#x2F;kits&#x2F;&quot;&gt;&lt;strong&gt;kits&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
Kits bundle together multiple constructors commonly used together for a specific purpose.
I started with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;dependency_injection&#x2F;core_concepts&#x2F;kits&#x2F;#apikit&quot;&gt;&lt;strong&gt;ApiKit&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;:
all the first-party constructors provided by Pavex for building REST APIs.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::blueprint::Blueprint;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::kit::ApiKit;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 👇 Path params, query params, body extractors, etc.
&lt;&#x2F;span&gt;&lt;span&gt;  ApiKit::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;register&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; bp);
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When generating a new project via &lt;code&gt;pavex new&lt;&#x2F;code&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;65a8512d8d81672d2b15831dcbffde14e90b6d92&#x2F;examples&#x2F;starter&#x2F;app&#x2F;src&#x2F;blueprint.rs#L9&quot;&gt;&lt;code&gt;ApiKit&lt;&#x2F;code&gt; will be automatically installed&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pavex-tracing&quot;&gt;&lt;code&gt;pavex_tracing&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve released a new crate:
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;pavex_tracing&#x2F;0.1.21&#x2F;pavex_tracing&#x2F;&quot;&gt;&lt;code&gt;pavex_tracing&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
It bundles &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;pavex_tracing&#x2F;0.1.21&#x2F;pavex_tracing&#x2F;struct.RootSpan.html&quot;&gt;&lt;code&gt;RootSpan&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;,
the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;pavex_tracing&#x2F;0.1.21&#x2F;pavex_tracing&#x2F;fn.logger.html&quot;&gt;default logger middleware&lt;&#x2F;a&gt;, and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;65a8512d8d81672d2b15831dcbffde14e90b6d92&#x2F;examples&#x2F;starter&#x2F;app&#x2F;src&#x2F;telemetry.rs#L34&quot;&gt;helper functions
to log common fields&lt;&#x2F;a&gt;
according to OpenTelemetry&#x27;s conventions.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks to &lt;code&gt;pavex_tracing&lt;&#x2F;code&gt; 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.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;error-observers&quot;&gt;Error observers&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex relies on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;errors&#x2F;error_handlers&#x2F;&quot;&gt;error handlers&lt;&#x2F;a&gt; to convert errors into responses.&lt;br &#x2F;&gt;
Who is in charge of logging those errors, though?&lt;br &#x2F;&gt;
That&#x27;s the job of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;errors&#x2F;error_observers&#x2F;&quot;&gt;error observers&lt;&#x2F;a&gt;, the latest addition to
Pavex&#x27;s observability story.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_tracing::fields::{
&lt;&#x2F;span&gt;&lt;span&gt;    error_details, error_message, error_source_chain, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_DETAILS&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_MESSAGE&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_SOURCE_CHAIN 
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_tracing::RootSpan;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; An error observer to log error details.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; It emits an error event and attaches information about the error to the root span.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; If multiple errors are observed for the same request, it will emit multiple error events
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; but only the details of the last error will be attached to the root span.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;error_logger&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;pavex::Error, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;root_span&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RootSpan) {
&lt;&#x2F;span&gt;&lt;span&gt;    tracing::event!(
&lt;&#x2F;span&gt;&lt;span&gt;        tracing::Level::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_MESSAGE &lt;&#x2F;span&gt;&lt;span&gt;} = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_message&lt;&#x2F;span&gt;&lt;span&gt;(e),
&lt;&#x2F;span&gt;&lt;span&gt;        { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_DETAILS &lt;&#x2F;span&gt;&lt;span&gt;} = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_details&lt;&#x2F;span&gt;&lt;span&gt;(e),
&lt;&#x2F;span&gt;&lt;span&gt;        { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_SOURCE_CHAIN &lt;&#x2F;span&gt;&lt;span&gt;} = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_source_chain&lt;&#x2F;span&gt;&lt;span&gt;(e),
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;An error occurred during request handling&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    root_span.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_MESSAGE&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_message&lt;&#x2F;span&gt;&lt;span&gt;(e));
&lt;&#x2F;span&gt;&lt;span&gt;    root_span.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_DETAILS&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_details&lt;&#x2F;span&gt;&lt;span&gt;(e));
&lt;&#x2F;span&gt;&lt;span&gt;    root_span.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ERROR_SOURCE_CHAIN&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_source_chain&lt;&#x2F;span&gt;&lt;span&gt;(e));
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I wrote an &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.lpalmieri.com&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;&quot;&gt;exhaustive post on error reporting&lt;&#x2F;a&gt;
earlier this month. Check it out to see how Pavex&#x27;s approach compares to existing Rust web frameworks.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;terser-registrations&quot;&gt;Terser registrations&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve started a little crusade against verbosity.
Some things can feel boilerplate-y when working with Pavex, so I&#x27;ve added some sugar:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You no longer need to use a fully-qualified path
(e.g. &lt;code&gt;crate::request::path::PathParams::extract&lt;&#x2F;code&gt;) to register a component.
You can use a &lt;strong&gt;relative&lt;&#x2F;strong&gt; path that starts with either &lt;code&gt;self&lt;&#x2F;code&gt; (relative to the current module) or &lt;code&gt;super&lt;&#x2F;code&gt;
(relative to the parent module).&lt;&#x2F;li&gt;
&lt;li&gt;You can use a lifecycle-specific shorthand when registering constructors (e.g. &lt;code&gt;Blueprint::request_scoped&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Registrations look a lot terser!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Before 😔
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;   f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::request::path::PathParams::extract), 
&lt;&#x2F;span&gt;&lt;span&gt;   Lifecycle::RequestScoped
&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_handler&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;   f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::request::path::errors::ExtractPathParamsError::into_response)
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; After 🚀
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;request_scoped&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;super&lt;&#x2F;span&gt;&lt;span&gt;::PathParams::extract))
&lt;&#x2F;span&gt;&lt;span&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_handler&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;super&lt;&#x2F;span&gt;&lt;span&gt;::errors::ExtractPathParamsError::into_response));
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;ci&quot;&gt;CI&lt;&#x2F;h2&gt;
&lt;p&gt;The project generated by &lt;code&gt;pavex new&lt;&#x2F;code&gt; (that you can browse &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;65a8512d8d81672d2b15831dcbffde14e90b6d92&#x2F;examples&#x2F;starter&quot;&gt;on GitHub&lt;&#x2F;a&gt;)
now includes a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;main&#x2F;examples&#x2F;starter&#x2F;.github&#x2F;workflows&quot;&gt;CI pipeline for GitHub Actions&lt;&#x2F;a&gt;
as well as &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;main&#x2F;examples&#x2F;starter&#x2F;Dockerfile&quot;&gt;a Dockerfile&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;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.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;windows&quot;&gt;Windows&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex&#x27;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.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily enough, that&#x27;s a problem of the past: I&#x27;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&#x27;s always a first time!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;miscellaneous&quot;&gt;Miscellaneous&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s too much to cover everything in details, but a few other changes that landed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;We now have &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;errors&#x2F;error_handlers&#x2F;#universal&quot;&gt;universal error handlers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;e8ec6f78eab092b8d4354f95ad6c2cfee8b9b8e9&quot;&gt;Our user-facing errors have improved&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Pavex now &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;06f156282f7768f228b3c251c647ccb23b372fed&quot;&gt;works nicely with &lt;code&gt;cargo-watch&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Pavex &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;84131e0bc939e489f670611cf6c55a761a5c77da&quot;&gt;emits a warning if you register a constructor that&#x27;s never used&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Better &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;a30962df8be0e6dec5fc8fad2a2b46eeec1571fe&quot;&gt;configuration setup&lt;&#x2F;a&gt; for the starter project&lt;&#x2F;li&gt;
&lt;li&gt;...and a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;12c2ca9dc833b95e8dd84b70fb8f1126a002e257&quot;&gt;lot&lt;&#x2F;a&gt; &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;768fe1bd594c5476d9d4f2c6d9c0c912afadb0fc&quot;&gt;of&lt;&#x2F;a&gt;
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;3e82b63579f46e7e31c5b53cff575871dc54fe05&quot;&gt;bug&lt;&#x2F;a&gt; &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;d14d2f8a6064cc321b4111dc9d4c8a6b1f2b4a85&quot;&gt;fixes&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h2&gt;
&lt;p&gt;The dependency injection engine is getting more stable and it provides most of the functionality I think we&#x27;ll need,
thus I can start spending my time elsewhere.
I already started this month (see the work on observability), but I&#x27;ll be doubling down next month: features, features, features!&lt;br &#x2F;&gt;
The list is quite long: cookies, sessions, TLS, compression, etc. It&#x27;ll be fun!
When I&#x27;m done, you should be able to build a backend + website using only Pavex&#x27;s first-party crates. I&#x27;ll be dogfooding it all with Pavex&#x27;s website.&lt;&#x2F;p&gt;
&lt;p&gt;At the end of March I&#x27;ll also be at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rustnationuk.com&#x2F;&quot;&gt;RustNation UK&lt;&#x2F;a&gt;
to give &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rustnationuk.com&#x2F;schedule&quot;&gt;a talk about Pavex&lt;&#x2F;a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#workshop&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
It&#x27;ll be an updated version of what I covered at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rustlab.it&#x2F;&quot;&gt;RustLab&lt;&#x2F;a&gt; in November&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#recording&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
If you&#x27;re coming to the conference, happy to meet and chat about Pavex (or Rust in general).&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1b0k3y3&#x2F;this_month_in_pavex_9_rust_web_framework&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;recording&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;The recording has recently been &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=r1nRC32MHpc&quot;&gt;uploaded to YouTube&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;workshop&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;I&#x27;ll also lead the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rustnationuk.com&#x2F;workshops&quot;&gt;expert-level workshop on testing&lt;&#x2F;a&gt;.
There are a few tickets left; if they run out, you can join &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ti.to&#x2F;mainmatter&#x2F;rust-testing-april-2024&quot;&gt;the remote version&lt;&#x2F;a&gt;
in April if you&#x27;re interested.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2024-02-25T08:00:10.47Z&quot;&gt;February 2024&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-is-in-closed-beta&amp;#x2F;&quot;&gt;Pavex is in closed beta&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;this-month-in-pavex-10&amp;#x2F;&quot;&gt;This month in Pavex, #10&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Rust web frameworks have subpar error reporting</title>
          <pubDate>Mon, 05 Feb 2024 12:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/rust-web-frameworks-have-subpar-error-reporting/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/rust-web-frameworks-have-subpar-error-reporting/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/rust-web-frameworks-have-subpar-error-reporting/">&lt;p&gt;None of the major Rust web frameworks have a &lt;em&gt;great&lt;&#x2F;em&gt; error reporting story, according to
my personal definition of great.&lt;br &#x2F;&gt;
I&#x27;ve been building production APIs in Rust for almost 6 years now, and I&#x27;ve been &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;teaching people about
backend development in Rust&lt;&#x2F;a&gt; for almost as long: I&#x27;ve always had to tweak, work-around or
actively fight the framework to get &lt;strong&gt;reliable&lt;&#x2F;strong&gt; and &lt;strong&gt;exhaustive&lt;&#x2F;strong&gt; error reporting.&lt;&#x2F;p&gt;
&lt;p&gt;Last year I bit the bullet and started building my own web framework, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;.
I channeled my frustration into a different error reporting design.
This post sums up the journey and the rationale behind it.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this post on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1ajm86e&#x2F;rust_web_frameworks_have_subpar_error_reporting&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of contents&lt;&#x2F;h2&gt;
&lt;!-- TOC --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#what-are-errors-for&quot;&gt;What are errors &lt;em&gt;for&lt;&#x2F;em&gt;?&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#reacting&quot;&gt;Reacting&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#reporting&quot;&gt;Reporting&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#modelling-errors-in-rust&quot;&gt;Modelling errors in Rust&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#the-error-trait&quot;&gt;The &lt;code&gt;Error&lt;&#x2F;code&gt; trait&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#our-benchmark&quot;&gt;Our benchmark&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#high-level-requirements&quot;&gt;High-level requirements&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#low-level-requirements&quot;&gt;Low-level requirements&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#frameworks&quot;&gt;Frameworks&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#axum&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#request-handlers&quot;&gt;Request handlers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#intoresponse&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#extractors&quot;&gt;Extractors&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#can-axum-meet-our-requirements&quot;&gt;Can &lt;code&gt;axum&lt;&#x2F;code&gt; meet our requirements?&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#workaround-1&quot;&gt;Workaround #1&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#workaround-2&quot;&gt;Workaround #2&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#actix-web&quot;&gt;Actix Web&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#actix-web-request-handlers&quot;&gt;Request handlers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#responder&quot;&gt;&lt;code&gt;Responder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#responseerror&quot;&gt;&lt;code&gt;ResponseError&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#httpresponse-error&quot;&gt;&lt;code&gt;HttpResponse::error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#can-actix-web-meet-our-requirements&quot;&gt;Can Actix Web meet our requirements?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#error-requirements&quot;&gt;Error requirements&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#intoresponse&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#error-handlers&quot;&gt;Error handlers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#error-observers&quot;&gt;Error observers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#can-pavex-meet-our-requirements&quot;&gt;Can Pavex meet our requirements?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#conclusion&quot;&gt;Conclusion&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;!-- TOC --&gt;
&lt;h2 id=&quot;what-are-errors-for&quot;&gt;What are errors &lt;em&gt;for&lt;&#x2F;em&gt;?&lt;&#x2F;h2&gt;
&lt;p&gt;So many different things can go wrong in a networked application: the database is down (or slow), the caller
sent invalid data, you ran out of file descriptors, etc.&lt;br &#x2F;&gt;
Every time something goes wrong, two different concerns must be addressed: &lt;strong&gt;reacting&lt;&#x2F;strong&gt; and &lt;strong&gt;reporting&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;reacting&quot;&gt;Reacting&lt;&#x2F;h3&gt;
&lt;p&gt;Whoever called your API is waiting for a response!&lt;br &#x2F;&gt;
Your application needs to &lt;strong&gt;convert the error into a response&lt;&#x2F;strong&gt;, using a representation that the caller can understand.&lt;&#x2F;p&gt;
&lt;p&gt;For an HTTP API, this involves selecting the most appropriate status code (e.g. &lt;code&gt;500 Internal Server Error&lt;&#x2F;code&gt; or
&lt;code&gt;400 Bad Request&lt;&#x2F;code&gt;) and, if required, a more detailed error message in the body (e.g. an explanation of which
field was invalid and why).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;reporting&quot;&gt;Reporting&lt;&#x2F;h3&gt;
&lt;p&gt;At the same time, as an &lt;strong&gt;operator&lt;&#x2F;strong&gt; (i.e. the person responsible for keeping the application up and running), you need
to have &lt;strong&gt;a mechanism to know that an error occurred&lt;&#x2F;strong&gt;.
For example, you might track the percentage of 5xx errors to page an on-call engineer if it goes above a pre-defined
threshold.&lt;&#x2F;p&gt;
&lt;p&gt;Knowing that an error occurred is not enough though: you need to know &lt;strong&gt;what went wrong&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
When that engineer gets paged, or when you get to work in the morning, there has to be &lt;strong&gt;enough information
to troubleshoot the issue&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;modelling-errors-in-rust&quot;&gt;Modelling errors in Rust&lt;&#x2F;h3&gt;
&lt;p&gt;Rust has two ways to model failures: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;macro.panic.html&quot;&gt;panics&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;result&#x2F;enum.Result.html&quot;&gt;&lt;code&gt;Result&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
Panics are primarily used for unrecoverable errors, so I won&#x27;t discuss them much here—you need to recover and
send a response! Let&#x27;s focus on &lt;code&gt;Result&lt;&#x2F;code&gt; instead.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;result&#x2F;enum.Result.html&quot;&gt;&lt;code&gt;Result&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a type, an enum.
It has two variants: success (&lt;code&gt;Ok&lt;&#x2F;code&gt;) or failure (&lt;code&gt;Err&lt;&#x2F;code&gt;).
When &lt;strong&gt;a function can fail, it shows in its signature&lt;&#x2F;strong&gt;: it uses a &lt;code&gt;Result&lt;&#x2F;code&gt; as its return type.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;There&#x27;s a lot to be said about good error design as a prerequisite to good error reporting, but that&#x27;d be too much of a detour.
If you want to learn more about error design, check out &lt;a href=&quot;&#x2F;posts&#x2F;error-handling-rust&quot;&gt;this previous post of mine&lt;&#x2F;a&gt;—it
builds on the same principles.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;the-error-trait&quot;&gt;The &lt;code&gt;Error&lt;&#x2F;code&gt; trait&lt;&#x2F;h3&gt;
&lt;p&gt;There are no constraints on the type of the &lt;code&gt;Err&lt;&#x2F;code&gt; variant, but it&#x27;s a good practice to use a type that implements
the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;error&#x2F;trait.Error.html&quot;&gt;&lt;code&gt;std::error::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait.&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;error&#x2F;trait.Error.html&quot;&gt;&lt;code&gt;std::error::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is the cornerstone of Rust&#x27;s error handling story.
It requires error types to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Implement the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;trait.Display.html&quot;&gt;&lt;code&gt;Display&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait, as its &lt;strong&gt;user-facing representation&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Implement the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;trait.Debug.html&quot;&gt;&lt;code&gt;Debug&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait, as its &lt;strong&gt;operator-facing representation&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Provide a way to access the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;error&#x2F;trait.Error.html#method.source&quot;&gt;&lt;strong&gt;source of the error&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, if any&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The last point is particularly important: error types are often &lt;strong&gt;wrappers around lower-level errors&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
For example, a database connection error might be caused by a network error, which is in turn caused by a DNS resolution
issue. When troubleshooting, you want to be able to &lt;strong&gt;drill down&lt;&#x2F;strong&gt; into the chain of causes.
You can&#x27;t fix that database connection error if your logs don&#x27;t show that it was caused by a DNS resolution
issue in the first place!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;our-benchmark&quot;&gt;Our benchmark&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;high-level-requirements&quot;&gt;High-level requirements&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s set some expectations to properly &quot;benchmark&quot; the error reporting story of different web frameworks.&lt;br &#x2F;&gt;
At a high level, we want the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;All errors are logged, exactly once, with enough information to troubleshoot&lt;&#x2F;li&gt;
&lt;li&gt;With a single log line, we can tell:
&lt;ul&gt;
&lt;li&gt;If the request failed&lt;&#x2F;li&gt;
&lt;li&gt;What error occurred&lt;&#x2F;li&gt;
&lt;li&gt;What caused the error&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It should be possible to ensure that these requirements are met with &lt;strong&gt;minimum room for error&lt;&#x2F;strong&gt;—it shouldn&#x27;t be
possible to forget to log an error, or to log it in a way that&#x27;s inconsistent with the rest of the application.&lt;&#x2F;p&gt;
&lt;p&gt;I consider this the bare minimum telemetry setup for a production-grade application. I don&#x27;t expect a web framework
to provide this experience out of the box (although it&#x27;d be nice!), but I do expect it to provide the necessary
hooks to build it myself.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;low-level-requirements&quot;&gt;Low-level requirements&lt;&#x2F;h3&gt;
&lt;p&gt;We can convert this high-level specification into a set of concrete requirements:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;For every incoming request, there is an over-arching &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tracing&#x2F;latest&#x2F;tracing&#x2F;span&#x2F;index.html&quot;&gt;&lt;code&gt;tracing::Span&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
that captures the entire request lifecycle.
I&#x27;ll refer to this as the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;stripe.com&#x2F;blog&#x2F;canonical-log-lines&quot;&gt;&lt;strong&gt;root span&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Every time an error occurs, the application emits a &lt;code&gt;tracing&lt;&#x2F;code&gt; event:
&lt;ol&gt;
&lt;li&gt;Its level is set to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tracing&#x2F;latest&#x2F;tracing&#x2F;struct.Level.html#associatedconstant.ERROR&quot;&gt;&lt;code&gt;ERROR&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;trait.Display.html&quot;&gt;&lt;code&gt;Display&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; representation of the error in recorded in the event&#x27;s &lt;code&gt;error.msg&lt;&#x2F;code&gt; field&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;trait.Debug.html&quot;&gt;&lt;code&gt;Debug&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; representation of the error in recorded in the event&#x27;s &lt;code&gt;error.details&lt;&#x2F;code&gt; field&lt;&#x2F;li&gt;
&lt;li&gt;The chain of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;error&#x2F;trait.Error.html#method.source&quot;&gt;sources&lt;&#x2F;a&gt; (if any) is recorded in the event&#x27;s &lt;code&gt;error.source_chain&lt;&#x2F;code&gt; field&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;For the error that was converted into the HTTP response returned to the caller, we capture:
&lt;ol&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;trait.Display.html&quot;&gt;&lt;code&gt;Display&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; representation in the root span&#x27;s &lt;code&gt;error.msg&lt;&#x2F;code&gt; field&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;fmt&#x2F;trait.Debug.html&quot;&gt;&lt;code&gt;Debug&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; representation in the root span&#x27;s &lt;code&gt;error.details&lt;&#x2F;code&gt; field&lt;&#x2F;li&gt;
&lt;li&gt;The chain of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;error&#x2F;trait.Error.html#method.source&quot;&gt;sources&lt;&#x2F;a&gt; in the root span&#x27;s &lt;code&gt;error.source_chain&lt;&#x2F;code&gt; field&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;ve been using &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tracing&#x2F;latest&#x2F;tracing&#x2F;&quot;&gt;&lt;code&gt;tracing&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; as the structured library of choice here,
but the same requirements can be expressed in terms of other logging libraries (and the framework should
be able to integrate with them!).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;frameworks&quot;&gt;Frameworks&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ll start by reviewing how &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;actix.rs&#x2F;&quot;&gt;Actix Web&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tokio-rs&#x2F;axum&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, the two most popular web frameworks in the Rust ecosystem, fare against these
requirements&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#rocket&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
I&#x27;ll then discuss &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;&#x27;s approach.&lt;&#x2F;p&gt;
&lt;p&gt;If you don&#x27;t care about the details, you can skip to the &lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#conclusion&quot;&gt;conclusion&lt;&#x2F;a&gt; to see how the frameworks compare.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;axum&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tokio-rs&#x2F;axum&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, the following components can fail:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#request-handlers&quot;&gt;Request handlers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#extractors&quot;&gt;Extractors&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Middlewares&#x2F;arbitrary &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tower&#x2F;&quot;&gt;&lt;code&gt;tower&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; services&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s overall error handling approach is detailed in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;error_handling&#x2F;index.html&quot;&gt;their documentation&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
I&#x27;ll focus on request handlers and extractors, as they&#x27;re the most common error sources in applications.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;actix-web-request-handlers&quot;&gt;Request handlers&lt;&#x2F;h3&gt;
&lt;p&gt;In &lt;code&gt;axum&lt;&#x2F;code&gt;, request handlers are asynchronous functions that return a type that implements
the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;intoresponse&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a &lt;strong&gt;conversion trait&lt;&#x2F;strong&gt;: it specifies how to convert a type into an HTTP response.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;IntoResponse {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;into_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Response&amp;lt;Body&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Result&lt;&#x2F;code&gt; implements &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, as long as
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html#impl-IntoResponse-for-Result%3CT,+E%3E&quot;&gt;both the &lt;code&gt;Ok&lt;&#x2F;code&gt; and &lt;code&gt;Err&lt;&#x2F;code&gt; variants do&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Once &lt;code&gt;IntoResponse::into_response&lt;&#x2F;code&gt; has been called (by the framework), the type is gone—&lt;code&gt;self&lt;&#x2F;code&gt; is consumed.
From an error reporting perspective, this means that &lt;strong&gt;you can&#x27;t manipulate the error anymore&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;extractors&quot;&gt;Extractors&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;extract&#x2F;index.html#intro&quot;&gt;Extractors&lt;&#x2F;a&gt; are &lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s dependency injection mechanism.&lt;br &#x2F;&gt;
They&#x27;re used to extract data from the request (e.g. the query string, the request body, etc.) or to reject
the request if it doesn&#x27;t meet certain criteria (e.g. it&#x27;s missing an &lt;code&gt;Authorization&lt;&#x2F;code&gt; header).&lt;&#x2F;p&gt;
&lt;p&gt;You define an extractor by implementing either the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;extract&#x2F;trait.FromRequest.html&quot;&gt;&lt;code&gt;FromRequest&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
or the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;extract&#x2F;trait.FromRequestParts.html&quot;&gt;&lt;code&gt;FromRequestParts&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; traits.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Slightly simplified for exposition purposes
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;FromRequest&amp;lt;S&amp;gt;: Sized {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; If the extractor fails it&amp;#39;ll use this &amp;quot;rejection&amp;quot; type. A rejection is
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; a kind of error that can be converted into a response.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Rejection: IntoResponse;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; Perform the extraction.
&lt;&#x2F;span&gt;&lt;span&gt;    async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;from_request&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;S) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Rejection&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Error handling works similarly to request handlers: if the extractor fails, it must return an error type that implements
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. Therefore, it suffers from the same limitations.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-axum-meet-our-requirements&quot;&gt;Can &lt;code&gt;axum&lt;&#x2F;code&gt; meet our requirements?&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;axum&lt;&#x2F;code&gt; provides no mechanism to execute logic between the request handler returning an error, and that very same
error being converted into an HTTP response via &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse::into_response&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
The same is true for extractors.&lt;br &#x2F;&gt;
If you want to log errors, you must do it:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In your request handler&#x2F;extractor&lt;&#x2F;li&gt;
&lt;li&gt;Inside the &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; implementation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Neither is ideal.&lt;&#x2F;p&gt;
&lt;p&gt;You don&#x27;t have a &lt;strong&gt;single place&lt;&#x2F;strong&gt; where the logging logic lives&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#trace-layer&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
You end up with log statements spread out across the entire codebase.
It&#x27;s easy for an error to slip through the cracks, unlogged, or for logging logic to evolve inconsistently over time.&lt;br &#x2F;&gt;
Things get worse if you use error types defined in other crates—you can&#x27;t add logging to their &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; implementation,
nor customize it if it&#x27;s there. Perhaps they are emitting a &lt;code&gt;tracing&lt;&#x2F;code&gt; error event, but they aren&#x27;t using the same field names or they aren&#x27;t
recording the source chain.&lt;&#x2F;p&gt;
&lt;p&gt;Out of the box, &lt;code&gt;axum&lt;&#x2F;code&gt; comes quite short of meeting &lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#low-level-requirements&quot;&gt;the telemetry requirements&lt;&#x2F;a&gt; I laid down.
You can try to implement some mitigation strategies, described below, but neither is bullet-proof.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;workaround-1&quot;&gt;Workaround #1&lt;&#x2F;h3&gt;
&lt;p&gt;You can try to wrap&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#single-error-type&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; all your errors with a single custom error type (e.g. &lt;code&gt;ErrorLogger&amp;lt;E&amp;gt;&lt;&#x2F;code&gt;).
You then implement &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; for the wrapper and add the logging logic there.&lt;br &#x2F;&gt;
This still isn&#x27;t a bulletproof solution:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You may forget to wrap one of your errors with the custom error wrapper.&lt;&#x2F;li&gt;
&lt;li&gt;You can no longer use extractors defined in other crates (including &lt;code&gt;axum&lt;&#x2F;code&gt; itself!).
You need to wrap &lt;em&gt;all third-party extractors&lt;&#x2F;em&gt; to ensure they return a wrapped error.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This workaround, even if applied correctly, would still fail to meet &lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#low-level-requirements&quot;&gt;all our requirements&lt;&#x2F;a&gt;:
from inside &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; you can&#x27;t access extractors, therefore
you have no way to reliably access the root span for the current request and attach error details to it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;workaround-2&quot;&gt;Workaround #2&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Later edit: this approach was suggested in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1ajm86e&#x2F;rust_web_frameworks_have_subpar_error_reporting&#x2F;&quot;&gt;r&#x2F;rust comment section&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The approach above can be refined using &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;latest&#x2F;axum&#x2F;response&#x2F;type.Response.html#method.extensions&quot;&gt;&lt;code&gt;Response&lt;&#x2F;code&gt;&#x27;s extensions&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
You still need to wrap all errors with a custom wrapper,
but you don&#x27;t eagerly log the error inside &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
You instead store&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#store&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; the error in the extensions attached to the &lt;code&gt;Response&lt;&#x2F;code&gt;.
A logging middleware then tries to extract the error type from the extensions to log it.&lt;&#x2F;p&gt;
&lt;p&gt;The middleware can access the root span, coming closer to meeting our requirements.&lt;br &#x2F;&gt;
The underlying challenges remain unresolved: there is no reliable way to ensure you wrapped
&lt;em&gt;all&lt;&#x2F;em&gt; errors and you need to wrap all third-party extractors, including those defined in &lt;code&gt;axum&lt;&#x2F;code&gt; itself.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;actix-web&quot;&gt;Actix Web&lt;&#x2F;h2&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;actix.rs&#x2F;&quot;&gt;Actix Web&lt;&#x2F;a&gt;, the following components can fail:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Request handlers&lt;&#x2F;li&gt;
&lt;li&gt;Extractors&lt;&#x2F;li&gt;
&lt;li&gt;Middlewares&#x2F;arbitrary &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;dev&#x2F;trait.Service.html&quot;&gt;Service&lt;&#x2F;a&gt; implementations&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Actix Web&#x27;s overall error handling approach is detailed &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;actix.rs&#x2F;docs&#x2F;errors&quot;&gt;on their website&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
Just like with &lt;code&gt;axum&lt;&#x2F;code&gt;, I&#x27;ll focus on request handlers and extractors,
as they&#x27;re the most common error sources in applications.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;request-handlers&quot;&gt;Request handlers&lt;&#x2F;h3&gt;
&lt;p&gt;In Actix Web, request handlers are asynchronous functions that return a type that implements
the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.Responder.html&quot;&gt;&lt;code&gt;Responder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;responder&quot;&gt;&lt;code&gt;Responder&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;Responder {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Body: MessageBody + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Required method
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;respond_to&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;HttpRequest) -&amp;gt; HttpResponse&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Body&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.Responder.html&quot;&gt;&lt;code&gt;Responder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a &lt;strong&gt;conversion trait&lt;&#x2F;strong&gt;: it specifies how to convert a type into an HTTP response.
Just like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;0.7.4&#x2F;axum&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, once &lt;code&gt;Responder::respond_to&lt;&#x2F;code&gt; has been called (by the framework),
the type is gone—&lt;code&gt;self&lt;&#x2F;code&gt; is consumed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Result&lt;&#x2F;code&gt; implements &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.Responder.html&quot;&gt;&lt;code&gt;Responder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, as long as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;Ok&lt;&#x2F;code&gt; variant implements &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.Responder.html&quot;&gt;&lt;code&gt;Responder&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;the &lt;code&gt;Err&lt;&#x2F;code&gt; variant implements the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.ResponseError.html&quot;&gt;&lt;code&gt;ResponseError&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;responseerror&quot;&gt;&lt;code&gt;ResponseError&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.ResponseError.html&quot;&gt;&lt;code&gt;ResponseError&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is another &lt;strong&gt;conversion trait&lt;&#x2F;strong&gt;, specialised for errors—it provides a cheap way
to check the status code of the resulting response without having to build it wholesale.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;ResponseError: Debug + Display {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;status_code&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; StatusCode;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;error_response&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; HttpResponse&amp;lt;BoxBody&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notice one key detail: neither &lt;code&gt;status_code&lt;&#x2F;code&gt; nor &lt;code&gt;error_response&lt;&#x2F;code&gt; consume &lt;code&gt;self&lt;&#x2F;code&gt;. They both &lt;strong&gt;take a reference to the
error type as input&lt;&#x2F;strong&gt;. You might be thinking: &quot;It doesn&#x27;t matter, &lt;code&gt;Responder::respond_to&lt;&#x2F;code&gt; consumes &lt;code&gt;self&lt;&#x2F;code&gt; anyway, so we can&#x27;t
log the error anymore!&quot;&lt;br &#x2F;&gt;
But here comes the twist: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;struct.HttpResponse.html#method.error&quot;&gt;&lt;code&gt;HttpResponse::error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;httpresponse-error&quot;&gt;&lt;code&gt;HttpResponse::error&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;In Actix Web, when an &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;struct.HttpResponse.html&quot;&gt;&lt;code&gt;HttpResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is built from an error
(via &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;struct.HttpResponse.html#method.from_error&quot;&gt;&lt;code&gt;HttpResponse::from_error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;),
the &lt;strong&gt;error is stored as part of the response&lt;&#x2F;strong&gt;. You can still access the error after the response has been built!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;extractors-1&quot;&gt;Extractors&lt;&#x2F;h3&gt;
&lt;p&gt;In Actix Web, extractors are types that implement the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.FromRequest.html&quot;&gt;&lt;code&gt;FromRequest&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
In terms of error handling, they work similarly to request handlers:
if the extractor fails, it must return an error type that can be converted into &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;error&#x2F;struct.Error.html&quot;&gt;&lt;code&gt;actix_web::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
which is in turn converted into &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;struct.HttpResponse.html&quot;&gt;&lt;code&gt;HttpResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; via its &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.5.1&#x2F;actix_web&#x2F;trait.ResponseError.html&quot;&gt;&lt;code&gt;ResponseError&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; implementation.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-actix-web-meet-our-requirements&quot;&gt;Can Actix Web meet our requirements?&lt;&#x2F;h3&gt;
&lt;p&gt;Almost.&lt;br &#x2F;&gt;
You can write an Actix Web middleware that checks if the current response bundles an error and, if so, log it.&lt;br &#x2F;&gt;
That&#x27;s exactly what I did in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;tracing-actix-web&quot;&gt;&lt;code&gt;tracing-actix-web&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;tracing-actix-web&lt;&#x2F;code&gt; was indeed built to meet &lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#low-level-requirements&quot;&gt;the requirements&lt;&#x2F;a&gt; I set at the beginning of this post, but
&lt;strong&gt;it falls short&lt;&#x2F;strong&gt;: &lt;strong&gt;only the last error is going to be logged&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;You can see why that&#x27;s the case by following this scenario:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A request handler returns an error&lt;&#x2F;li&gt;
&lt;li&gt;The error is converted into an HTTP response and stored in the response&lt;&#x2F;li&gt;
&lt;li&gt;The response passes through an unrelated middleware, which fails&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#actix-middleware&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and builds a new response from the new error&lt;&#x2F;li&gt;
&lt;li&gt;The logging middleware sees the final response and logs the last error&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The logging middleware never gets a chance
to see the first error since the corresponding response has been thrown away.
This is unfortunately a fundamental limitation of Actix Web&#x27;s current error handling design.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pavex&quot;&gt;Pavex&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; is a new web framework I&#x27;m building. It&#x27;s currently going through a private beta, but you can find
the documentation &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;, the following components can fail:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;routing&#x2F;request_handlers&#x2F;&quot;&gt;Request handlers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;dependency_injection&#x2F;&quot;&gt;Constructors&lt;&#x2F;a&gt; (i.e. our equivalent of extractors)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;middleware&#x2F;&quot;&gt;Middlewares&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can find a detailed overview of the error handling story in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;guide&#x2F;errors&#x2F;&quot;&gt;documentation&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;error-requirements&quot;&gt;Error requirements&lt;&#x2F;h3&gt;
&lt;p&gt;There is only one requirement for errors in Pavex:
it must be possible to convert them into a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;struct.error&quot;&gt;&lt;code&gt;pavex::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; via &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;struct.error#method.new&quot;&gt;&lt;code&gt;pavex::Error::new&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
All errors that implement the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;error&#x2F;trait.Error.html&quot;&gt;&lt;code&gt;std::error::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait can be converted into a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;struct.error&quot;&gt;&lt;code&gt;pavex::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;,
as well as some other types
that can&#x27;t implement it directly—e.g. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;anyhow&#x2F;1.0.79&#x2F;anyhow&#x2F;struct.Error.html&quot;&gt;&lt;code&gt;anyhow::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; or
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;eyre&#x2F;0.6.12&#x2F;eyre&#x2F;struct.Report.html&quot;&gt;&lt;code&gt;eyre::Report&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;intoresponse-1&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Pavex, just like Actix Web and &lt;code&gt;axum&lt;&#x2F;code&gt;, has a &lt;strong&gt;conversion trait&lt;&#x2F;strong&gt; that specifies how to convert a type into an HTTP response:
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;IntoResponse {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Required method
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;into_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Response;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There&#x27;s a key difference though: &lt;strong&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is not implemented for &lt;code&gt;Result&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;error-handlers&quot;&gt;Error handlers&lt;&#x2F;h3&gt;
&lt;p&gt;To convert an error into an HTTP response, you must register &lt;strong&gt;an error handler&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::blueprint::router::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::blueprint::Blueprint;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::f;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The `handler` for the `&#x2F;login` route returns a `Result`
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;login&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::core::handler))
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We specify which function should be called to
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; convert the error into an HTTP response
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_handler&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::core::login_error2response));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;An error handler is a function or method that takes a &lt;strong&gt;reference to the error type&lt;&#x2F;strong&gt; and returns a type that implements
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;response&#x2F;trait.IntoResponse.html&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::http::StatusCode;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;login_error2response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;LoginError) -&amp;gt; StatusCode  {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; e {
&lt;&#x2F;span&gt;&lt;span&gt;        LoginError::InvalidCredentials =&amp;gt; StatusCode::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;UNAUTHORIZED&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        LoginError::DatabaseError =&amp;gt; StatusCode::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;INTERNAL_SERVER_ERROR&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;error-observers&quot;&gt;Error observers&lt;&#x2F;h3&gt;
&lt;p&gt;After Pavex has generated an HTTP response from the error, using the error handler you registered, it converts
your concrete error type into a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;struct.error&quot;&gt;&lt;code&gt;pavex::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and
invokes your &lt;strong&gt;error observers&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;log_error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;pavex::Error) {
&lt;&#x2F;span&gt;&lt;span&gt;    tracing::error!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;An error occurred: {}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, e);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;An error observer is a function or method that takes a &lt;strong&gt;reference to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&#x2F;docs&#x2F;api_reference&#x2F;pavex&#x2F;struct.error&quot;&gt;&lt;code&gt;pavex::Error&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; as input
and returns nothing.&lt;br &#x2F;&gt;
They are designed for error reporting—e.g. you can use them to log errors, increment a metric counter, etc.&lt;&#x2F;p&gt;
&lt;p&gt;You can register as many error observers as you want, and they will all be invoked in the order they were registered:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::blueprint::router::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::blueprint::Blueprint;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::f;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_observer&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::core::log_error));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;can-pavex-meet-our-requirements&quot;&gt;Can Pavex meet our requirements?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes!&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; invokes error observers for every error that occurs—by construction, you simply can&#x27;t forget an error
along the way.&lt;br &#x2F;&gt;
Error observers can take advantage of dependency injection, therefore they access the root span for the current request
and attach error details to it.
That&#x27;s exactly what happens in the starter project generated by &lt;code&gt;pavex new&lt;&#x2F;code&gt;, using the following error observer:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;log_error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;pavex::Error, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;root_span&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RootSpan) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; source_chain = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_source_chain&lt;&#x2F;span&gt;&lt;span&gt;(e);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Emit an error event
&lt;&#x2F;span&gt;&lt;span&gt;    tracing::error!(
&lt;&#x2F;span&gt;&lt;span&gt;        error.msg = %e,
&lt;&#x2F;span&gt;&lt;span&gt;        error.details = ?e,
&lt;&#x2F;span&gt;&lt;span&gt;        error.source_chain = %source_chain,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;An error occurred during request handling&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Attach the error details to the root span
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; If multiple errors occur, the details of the last one will &amp;quot;prevail&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    root_span.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;error.msg&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, tracing::field::display(e));
&lt;&#x2F;span&gt;&lt;span&gt;    root_span.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;error.details&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, tracing::field::debug(e));
&lt;&#x2F;span&gt;&lt;span&gt;    root_span.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;error.source_chain&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_source_chain&lt;&#x2F;span&gt;&lt;span&gt;(e));
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s all you need to meet &lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#low-level-requirements&quot;&gt;the requirements&lt;&#x2F;a&gt; I set at the beginning of this post.&lt;br &#x2F;&gt;
No workarounds, no sharp edges, no corner cases.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;It is not possible to fully and reliably satisfy &lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;rust-web-frameworks-have-subpar-error-reporting&#x2F;#low-level-requirements&quot;&gt;our telemetry requirements&lt;&#x2F;a&gt; with
either &lt;code&gt;axum&lt;&#x2F;code&gt; nor Actix Web.&lt;br &#x2F;&gt;
Actix Web comes much closer though: that&#x27;s why I still recommend Actix Web over &lt;code&gt;axum&lt;&#x2F;code&gt; when people ask me for advice
on which Rust web framework to use for their next project.
Solid error reporting is &lt;strong&gt;that&lt;&#x2F;strong&gt; important to me.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;, on the other hand, easily meets all the requirements.&lt;br &#x2F;&gt;
It&#x27;s not a coincidence: I&#x27;ve been building it with these requirements in mind from day one, making error reporting
a first-class concern. I&#x27;m confident to say that, right now, &lt;strong&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; has the best error
reporting story in the Rust web ecosystem&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Nonetheless, there is no intrinsic limitation preventing Actix Web or &lt;code&gt;axum&lt;&#x2F;code&gt; from converging to a similar design (or
perhaps a new one!) to resolve the issues I&#x27;ve highlighted in this post.&lt;br &#x2F;&gt;
I sincerely hope that happens—the main advantage of having different frameworks is the constant cross-pollination
of ideas and the pressure to improve.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this post on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1ajm86e&#x2F;rust_web_frameworks_have_subpar_error_reporting&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you want to be notified when a post is published!&lt;br &#x2F;&gt;
You can also follow the development of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;&#x2F;h2&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;rocket&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;I originally wanted to include &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rocket.rs&#x2F;&quot;&gt;Rocket&lt;&#x2F;a&gt; in this comparison, but I quickly realised that it doesn&#x27;t
provide enough hooks to even wrap a &lt;code&gt;tracing::Span&lt;&#x2F;code&gt; around the request-handling &lt;code&gt;Future&lt;&#x2F;code&gt;.
That&#x27;s a prerequisite to a correct implementation of structured logging, there&#x27;s no point in going further
without it.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;trace-layer&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;If you&#x27;re using &lt;code&gt;TraceLayer&lt;&#x2F;code&gt;, from &lt;code&gt;tower_http&lt;&#x2F;code&gt;, you might be wondering: isn&#x27;t that enough? Isn&#x27;t &lt;em&gt;that&lt;&#x2F;em&gt;
the single place? Unfortunately, &lt;code&gt;TraceLayer::on_failure&lt;&#x2F;code&gt; doesn&#x27;t get to see the error, it only looks at the response
generated by the error!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;single-error-type&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;There&#x27;s another variation of this approach: you return the same error type (e.g. &lt;code&gt;ApiError&lt;&#x2F;code&gt;) from
all your extractors, request handlers and middlewares. The two approaches are fundamentally equivalent.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;store&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;What&#x27;s stored inside &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;http&#x2F;1.0.0&#x2F;http&#x2F;struct.Extensions.html#method.insert&quot;&gt;&lt;code&gt;Extensions&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; has to
be clonable. This can be solved by wrapping the original error inside an &lt;code&gt;Arc&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;actix-middleware&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;There&#x27;s another issue with failures in Actix Web middlewares, but it&#x27;d take forever to get
into the details and explain it.
The TL;DR is that the invocation of the downstream portion of the middleware stack should return a &lt;code&gt;Response&lt;&#x2F;code&gt;
but it now returns a &lt;code&gt;Result&lt;&#x2F;code&gt;, creating a weird separate track for errors that&#x27;s hard to integrate with the
overall error handling story.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</description>
      </item>
      <item>
          <title>This month in Pavex, #8: docs, docs, docs</title>
          <pubDate>Sun, 07 Jan 2024 08:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-08/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-08/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/this-month-in-pavex-08/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is a progress report about &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;, a new Rust web framework that I have been working on.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;style scoped&gt;
    &#x2F;* 0 to 299 *&#x2F;
    .pavex_logo {
        max-width: 100%;
        object-fit: scale-down;
        width: 1000px;
        aspect-ratio: 1 &#x2F; 1;
    }
    &#x2F;* 300 to X *&#x2F;
    @media (min-width: 500px) {
        .pavex_logo {
            max-width: 50%;
        }
    }
&lt;&#x2F;style&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-beta&#x2F;pavex_logo.png&quot; alt=&quot;Pavex&#x27;s logo&quot; class=&quot;pavex_logo&quot;&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;It&#x27;s time for another progress report on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;Pavex&lt;&#x2F;a&gt;, covering what has been done in December!&lt;&#x2F;p&gt;
&lt;p&gt;At the end of November, I announced that Pavex was entering a &lt;a href=&quot;&#x2F;posts&#x2F;pavex-is-in-closed-beta&quot;&gt;closed beta period&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
From a functionality perspective, the framework was ready for a test drive.
Documentation, however, was lacking.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s where most of December went: docs, docs, docs.&lt;br &#x2F;&gt;
A month later, we&#x27;re in a good place&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#hosted&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. Good enough to kick off the closed beta&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#sign-up&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;algo_luca&#x2F;status&#x2F;1742629292683784459&quot;&gt;the first batch of invites went
out on January 3rd&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;I took a different approach to Pavex&#x27;s documentation compared to my previous OSS projects
and the work I did in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&#x2F;&quot;&gt;&quot;Zero to Production in Rust&quot;&lt;&#x2F;a&gt;.
I&#x27;ll use the rest of this post to dive into it!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;190up6c&#x2F;this_month_in_pavex_8_docs_docs_docs&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;!-- TOC --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#documentation-is-hard&quot;&gt;Documentation is hard&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#no-docs-no-users&quot;&gt;No docs, no users&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#what-docs-do-you-need&quot;&gt;What docs do you need?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#rustdoc-is-great-but-it-s-not-enough&quot;&gt;&lt;code&gt;rustdoc&lt;&#x2F;code&gt; is great, but it&#x27;s not enough&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#pavex-s-documentation&quot;&gt;Pavex&#x27;s documentation&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#stale-docs-are-the-worst&quot;&gt;Stale docs are the worst&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#test-all-the-things&quot;&gt;Test all the things&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;this-month-in-pavex-08&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;!-- TOC --&gt;
&lt;h2 id=&quot;documentation-is-hard&quot;&gt;Documentation is hard&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;no-docs-no-users&quot;&gt;No docs, no users&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;Documentation is one of the most important parts of a project&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
There&#x27;s plenty of great projects out there that aren&#x27;t realising their full potential
because people don&#x27;t know how to use them.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve always admired the Rust community&#x27;s emphasis on documentation.
Compared to other ecosystems, the average crate has better docs.
That&#x27;s definitely been a factor in Rust&#x27;s success and a testament to the excellent work done by the
&lt;code&gt;rustdoc&lt;&#x2F;code&gt; team: more maintainers are willing to write docs if it&#x27;s &lt;em&gt;easy&lt;&#x2F;em&gt; and they don&#x27;t
have to worry about &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&quot;&gt;hosting&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-docs-do-you-need&quot;&gt;What docs do you need?&lt;&#x2F;h3&gt;
&lt;p&gt;What makes documentation &lt;em&gt;good&lt;&#x2F;em&gt;? It&#x27;s a tough question to answer!&lt;br &#x2F;&gt;
You hear people praising this or that project&#x27;s documentation, but it&#x27;s often hard to articulate &lt;em&gt;why&lt;&#x2F;em&gt; it&#x27;s good.&lt;&#x2F;p&gt;
&lt;p&gt;While working at AWS, I was introduced to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;diataxis.fr&#x2F;&quot;&gt;Diátaxis&lt;&#x2F;a&gt;,
a systematic framework for writing documentation.
It starts with a simple premise: &lt;strong&gt;documentation must serve the needs of its users&lt;&#x2F;strong&gt;.
It then goes on to identify those needs
and define different types of documentation components that can be used to address them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;image&#x2F;pavex-report-07&#x2F;diataxis.png&quot; alt=&quot;The framework quadrant&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I leaned heavily on Diátaxis when structuring Pavex&#x27;s documentation.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;rustdoc-is-great-but-it-s-not-enough&quot;&gt;&lt;code&gt;rustdoc&lt;&#x2F;code&gt; is great, but it&#x27;s not enough&lt;&#x2F;h3&gt;
&lt;p&gt;The Rust community has a great tool for writing documentation: &lt;code&gt;rustdoc&lt;&#x2F;code&gt;.
It&#x27;s great at what it does: writing a comprehensive (and well-tested) API reference.
But &lt;strong&gt;users need more than an API reference&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;They need &lt;strong&gt;tutorials&lt;&#x2F;strong&gt;, teaching the basics as they walk you through a series of scripted steps.&lt;br &#x2F;&gt;
They need &lt;strong&gt;high-level explanations&lt;&#x2F;strong&gt;, covering the framework&#x27;s design and how everything fits together.&lt;br &#x2F;&gt;
They need &lt;strong&gt;how-to guides&lt;&#x2F;strong&gt;, tailored to the problem they&#x27;re trying to solve.&lt;&#x2F;p&gt;
&lt;p&gt;All these things are hard to write in &lt;code&gt;rustdoc&lt;&#x2F;code&gt; because &lt;code&gt;rustdoc&lt;&#x2F;code&gt; is &lt;strong&gt;code-oriented&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
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&#x27;s not necessarily
the best way to present the information to the user.&lt;br &#x2F;&gt;
You see Rust crates adding &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;clap&#x2F;4.4.13&#x2F;clap&#x2F;_faq&#x2F;index.html&quot;&gt;fake modules just to attach high-level documentation &lt;em&gt;somewhere&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pavex-s-documentation&quot;&gt;Pavex&#x27;s documentation&lt;&#x2F;h3&gt;
&lt;p&gt;For Pavex, I decided to augment the API reference generated by &lt;code&gt;rustdoc&lt;&#x2F;code&gt; with
a &lt;strong&gt;separate documentation site&lt;&#x2F;strong&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#hosted&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
It&#x27;s built with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;squidfunk.github.io&#x2F;mkdocs-material&#x2F;&quot;&gt;Material for MkDocs&lt;&#x2F;a&gt;, another
incredible tool for writing documentation.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;rustdoc&lt;&#x2F;code&gt; API reference is still there, but it&#x27;s just a part of the whole picture.&lt;br &#x2F;&gt;
And that picture is big! In just over a month, we have:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;~800 lines of code examples&lt;&#x2F;li&gt;
&lt;li&gt;~2000 lines of explanations, guides and tutorials&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;em&gt;on top of&lt;&#x2F;em&gt; the existing API reference and its doc examples.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stale-docs-are-the-worst&quot;&gt;Stale docs are the worst&lt;&#x2F;h3&gt;
&lt;p&gt;One of the biggest challenges with documentation is keeping it up to date.&lt;br &#x2F;&gt;
It&#x27;s incredibly frustrating to follow a tutorial and get stuck because the code that&#x27;s
being shown doesn&#x27;t compile anymore.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;rustdoc&lt;&#x2F;code&gt; is great at that: all code examples are compiled and tested every time you run &lt;code&gt;cargo test&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Things get a lot more complicated when you&#x27;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.&lt;br &#x2F;&gt;
This is a challenge I&#x27;m familiar with from my work on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&#x2F;&quot;&gt;&quot;Zero to Production in Rust&quot;&lt;&#x2F;a&gt;: you can
think of the book as a 600-page-long tutorial.
When I update a code snippet, I have to make sure I&#x27;ve updated all the other snippets that
reference it or relate to it in a later section or chapter.
It&#x27;s tricky and error-prone.&lt;&#x2F;p&gt;
&lt;p&gt;I wanted a better solution for Pavex.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;test-all-the-things&quot;&gt;Test all the things&lt;&#x2F;h3&gt;
&lt;p&gt;Every section of Pavex&#x27;s documentation site is backed by a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;main&#x2F;doc_examples&#x2F;quickstart&#x2F;tutorial.yml&quot;&gt;YAML manifest&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
In the manifest, we specify:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The state of the code at the beginning of the section (or a command to generate it)&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;starter_project_folder&lt;&#x2F;span&gt;&lt;span&gt;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;project&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;The code snippets that we want to extract from the project to be shown in the documentation&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;snippets&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;blueprint_definition&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;source_path&lt;&#x2F;span&gt;&lt;span&gt;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;demo&#x2F;src&#x2F;blueprint.rs&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ranges&lt;&#x2F;span&gt;&lt;span&gt;: [ &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;8..17&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; ]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# [...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Commands that we want to run on the code snippets (e.g. &lt;code&gt;cargo run&lt;&#x2F;code&gt;) and their expected outcome&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;commands&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;command&lt;&#x2F;span&gt;&lt;span&gt;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cargo px c&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;expected_outcome&lt;&#x2F;span&gt;&lt;span&gt;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;success&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# [...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;The changes that we want to apply to the starter project, as &lt;code&gt;git&lt;&#x2F;code&gt; patch files&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;diff --git a&#x2F;demo&#x2F;src&#x2F;blueprint.rs b&#x2F;demo&#x2F;src&#x2F;blueprint.rs
&lt;&#x2F;span&gt;&lt;span&gt;index e3623cd..c431f69 100644
&lt;&#x2F;span&gt;&lt;span&gt;--- a&#x2F;demo&#x2F;src&#x2F;blueprint.rs
&lt;&#x2F;span&gt;&lt;span&gt;+++ b&#x2F;demo&#x2F;src&#x2F;blueprint.rs
&lt;&#x2F;span&gt;&lt;span&gt;@@ -11,7 +11,8 @@ pub fn blueprint() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;bp.constructor(
&lt;&#x2F;span&gt;&lt;span&gt;f!(crate::user_agent::UserAgent::extract),
&lt;&#x2F;span&gt;&lt;span&gt;Lifecycle::RequestScoped,
&lt;&#x2F;span&gt;&lt;span&gt;-    );
&lt;&#x2F;span&gt;&lt;span&gt;+    )
&lt;&#x2F;span&gt;&lt;span&gt;+        .error_handler(f!(crate::user_agent::invalid_user_agent));
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;add_telemetry_middleware(&amp;amp;mut bp);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This manifest is fed into a custom tool I built, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;31e8b69b7d3c754a5f020a38450586bf1c8f1eb2&#x2F;doc_examples&#x2F;tutorial_generator&quot;&gt;&lt;code&gt;tutorial_generator&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, to generate the snippet files (&lt;code&gt;*.snap&lt;&#x2F;code&gt;)
that are then embedded into the documentation site using an &lt;code&gt;mkdocs&lt;&#x2F;code&gt; plugin:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;# Routing
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;## Route registration
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;All the routes exposed by your API must be registered with its &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`Blueprint`&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;][Blueprint]&lt;&#x2F;span&gt;&lt;span&gt;.  
&lt;&#x2F;span&gt;&lt;span&gt;In the snippet below you can see the registration of the &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`GET &#x2F;api&#x2F;ping`&lt;&#x2F;span&gt;&lt;span&gt; route, the one you targeted with your &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`curl`
&lt;&#x2F;span&gt;&lt;span&gt;request.
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;--8&amp;lt;-- &amp;quot;doc_examples&#x2F;quickstart&#x2F;demo-route_registration.snap&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If anything changes in Pavex (e.g. we modify the starter template returned by &lt;code&gt;pavex new&lt;&#x2F;code&gt;), 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&#x27;re not, we update the YAML manifest and regenerate the snippets by rerunning the command.&lt;&#x2F;p&gt;
&lt;p&gt;In CI, instead, we just make sure that the
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;31e8b69b7d3c754a5f020a38450586bf1c8f1eb2&#x2F;.github&#x2F;workflows&#x2F;docs.yml#L136&quot;&gt;snippets are up-to-date&lt;&#x2F;a&gt;:
if they&#x27;re not, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;actions&#x2F;runs&#x2F;7384485443&#x2F;job&#x2F;20087620177&quot;&gt;the build fails&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This system introduces a bit of complexity, but it&#x27;s worth it.&lt;br &#x2F;&gt;
I can make sweeping changes to the framework and be confident that I won&#x27;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!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m really excited to have the closed beta up and running!&lt;br &#x2F;&gt;
I&#x27;m looking forward to the feedback from the first cohorts of testers to help me shape the framework.&lt;&#x2F;p&gt;
&lt;p&gt;In the coming month, I&#x27;ll be splitting my focus on three fronts (in order of priority):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stability&lt;&#x2F;strong&gt;. As more people try to use the framework, we&#x27;ll find bugs&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#bugs&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. I want to patch any showstopping issues as soon as they arise.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;&#x2F;strong&gt;. I&#x27;ve done a lot in December, but there&#x27;s a few more sections I want to add (e.g. how to write middlewares, how to leverage &lt;code&gt;Blueprint&lt;&#x2F;code&gt; nesting to build modular applications, etc.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Gap analysis&lt;&#x2F;strong&gt;.
Can you migrate your existing projects to Pavex? Are we missing any core feature that would prevent you from doing so?
I&#x27;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!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That&#x27;s all for December, see you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;190up6c&#x2F;this_month_in_pavex_8_docs_docs_docs&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;hosted&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;The hosted version of the docs is not publicly available yet.
I&#x27;ll share it once we reach the open beta phase!
In the meantime, you can have a peek at it on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;master&#x2F;docs&quot;&gt;GitHub&lt;&#x2F;a&gt;
if you&#x27;re curious.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;sign-up&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;New batches of invites will be sent out every few weeks. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Sign up&lt;&#x2F;a&gt; to join the beta!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;bugs&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;It&#x27;s already happening (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;issues&#x2F;138&quot;&gt;1&lt;&#x2F;a&gt;,
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;issues&#x2F;134&quot;&gt;2&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;issues&#x2F;131&quot;&gt;3&lt;&#x2F;a&gt;) and so far the patching has been easy enough, thanks to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;m1guelpf&quot;&gt;Miguel&#x27;s&lt;&#x2F;a&gt;
incredible bug reports.
Thank you so much!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2024-01-07T08:00:10.47Z&quot;&gt;January 2024&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-is-in-closed-beta&amp;#x2F;&quot;&gt;Pavex is in closed beta&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Coming Soon&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Time to polish: Pavex is in closed beta 🎉</title>
          <pubDate>Mon, 27 Nov 2023 08:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-is-in-closed-beta/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-is-in-closed-beta/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-is-in-closed-beta/">&lt;style scoped&gt;
    &#x2F;* 0 to 299 *&#x2F;
    .pavex_logo {
        max-width: 100%;
        object-fit: scale-down;
        width: 1000px;
        aspect-ratio: 1 &#x2F; 1;
    }
    &#x2F;* 300 to X *&#x2F;
    @media (min-width: 500px) {
        .pavex_logo {
            max-width: 50%;
        }
    }
&lt;&#x2F;style&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-beta&#x2F;pavex_logo.png&quot; alt=&quot;Pavex&#x27;s logo&quot; class=&quot;pavex_logo&quot;&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;We&#x27;re starting a closed beta period for &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;!&lt;br &#x2F;&gt;
Head over to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;pavex.dev&lt;&#x2F;a&gt; to sign up for early access. The first batch of
invites will be sent out in a few weeks.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this post on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1856eey&#x2F;time_to_polish_pavex_is_in_closed_beta_rust_web&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;what-is-pavex&quot;&gt;What is Pavex?&lt;&#x2F;h1&gt;
&lt;p&gt;I want &lt;strong&gt;Rust&lt;&#x2F;strong&gt; to be &lt;strong&gt;a mainstream language for backend development&lt;&#x2F;strong&gt;: fast, safe, and productive.&lt;br &#x2F;&gt;
Pavex is my attempt at turning this ambition into a reality.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;batteries-included&quot;&gt;Batteries included&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex aims to be a &lt;strong&gt;one-stop shop&lt;&#x2F;strong&gt; for your API projects, providing all the batteries you need to build something production-ready.&lt;br &#x2F;&gt;
You can expect &lt;strong&gt;first-class solutions for common API development tasks&lt;&#x2F;strong&gt; (e.g. authentication, background jobs, telemetry, etc.)
or an &lt;strong&gt;opinionated recommendation&lt;&#x2F;strong&gt; for specific high-quality third-party libraries (e.g. async executors, cryptography, etc.),
with a smooth integration into the framework.&lt;&#x2F;p&gt;
&lt;p&gt;The first release of the framework will be focus on the foundations: manipulating HTTP requests and responses, routing,
middleware, dependency injection, error handling. We&#x27;ll then iterate on the framework to add more batteries as the
core layer stabilizes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;productive&quot;&gt;Productive&lt;&#x2F;h2&gt;
&lt;p&gt;Rust is a great language, but it has a large surface area: it&#x27;s easy to feel like you never know enough to actually
get started. Pavex aims to &lt;strong&gt;lower the barrier of entry&lt;&#x2F;strong&gt;: you&#x27;ll only need an &lt;strong&gt;understanding of Rust core concepts to get up and running&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
When things go wrong, we&#x27;ll be there to help you: &lt;strong&gt;Pavex&#x27;s transpiler&lt;&#x2F;strong&gt; will provide &lt;strong&gt;detailed error messages&lt;&#x2F;strong&gt; to help you
understand what went wrong and how to fix it.&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × I can&amp;#39;t invoke your wrapping middleware, `timeout`, because it needs an instance of
&lt;&#x2F;span&gt;&lt;span&gt;  │ `TimeoutConfig` as input, but I can&amp;#39;t find a constructor for that type.
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;blueprint.rs:18:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  18 │
&lt;&#x2F;span&gt;&lt;span&gt;  │  19 │     bp.wrap(f!(crate::timeout));
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·             ────────┬────────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                     ╰── The wrapping middleware was registered here
&lt;&#x2F;span&gt;&lt;span&gt;  │  20 │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │    ╭─[src&#x2F;load_shedding.rs:5:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  5 │
&lt;&#x2F;span&gt;&lt;span&gt;  │  6 │ pub async fn timeout&amp;lt;T&amp;gt;(next: Next&amp;lt;T&amp;gt;, timeout_config: TimeoutConfig) -&amp;gt; Response
&lt;&#x2F;span&gt;&lt;span&gt;  │    ·                                                        ──────┬──────
&lt;&#x2F;span&gt;&lt;span&gt;  │    ·                I don&amp;#39;t know how to construct an instance of this input parameter
&lt;&#x2F;span&gt;&lt;span&gt;  │    ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: Register a constructor for `TimeoutConfig`
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;safe&quot;&gt;Safe&lt;&#x2F;h2&gt;
&lt;p&gt;You can&#x27;t be productive if you spend most of your time hunting down bugs.&lt;br &#x2F;&gt;
Pavex&#x27;s transpiler will &lt;strong&gt;catch as many errors as possible at compile-time&lt;&#x2F;strong&gt;, providing you with actionable feedback
to fix them. This static analysis is on top of the usual Rust compiler checks: Pavex performs additional domain-specific
checks at compile-time to ensure that your code behaves as expected at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;This is all embedded into the framework. You don&#x27;t have to install any additional tools or plugins, and it doesn&#x27;t impact
the complexity of the framework&#x27;s API: we try to keep type complexity to a minimum, without sacrificing compile-time safety.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;flexible&quot;&gt;Flexible&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex is an &lt;strong&gt;opinionated framework&lt;&#x2F;strong&gt;, but it&#x27;s designed to be &lt;strong&gt;flexible&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
You can swap out any component of the framework with your own implementation, or defer to a third-party library.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s a pragmatic choice: we expect most projects to align with the vast majority of Pavex&#x27;s design decisions, but
each environment brings its own requirements (especially in enterprise) and we want to make sure that you can
adapt the framework to your needs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;how-do-i-try-it-out&quot;&gt;How do I try it out?&lt;&#x2F;h1&gt;
&lt;p&gt;Pavex&#x27;s source code in on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;, but neither the transpiler nor
the framework crates have been released yet.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ll use the closed beta period to &lt;strong&gt;polish the documentation&lt;&#x2F;strong&gt; and &lt;strong&gt;iterate on the foundational APIs&lt;&#x2F;strong&gt;. If you want to help us
out, head over to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;pavex.dev&lt;&#x2F;a&gt; and sign up for early access.&lt;br &#x2F;&gt;
The first batch of invites will be sent out in a few weeks: you&#x27;ll be invited to join a private server where
you&#x27;ll be able to discuss Pavex with the team and other beta testers, get access to the documentation, and
try out the framework.&lt;&#x2F;p&gt;
&lt;p&gt;See you there!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this post on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1856eey&#x2F;time_to_polish_pavex_is_in_closed_beta_rust_web&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-11-27T08:00:10.47Z&quot;&gt;November 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-06&amp;#x2F;&quot;&gt;Pavex report, #6&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;this-month-in-pavex-08&amp;#x2F;&quot;&gt;This month in Pavex, #8&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Crafting boring APIs: lessons learned from implementing fallback handlers in Pavex</title>
          <pubDate>Wed, 25 Oct 2023 08:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/api-design-should-be-boring/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/api-design-should-be-boring/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/api-design-should-be-boring/">&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Principle_of_least_astonishment&quot;&gt;principle of least surprise&lt;&#x2F;a&gt; is one of my north
stars when designing APIs: an interface should behave exactly as most people would expect it to behave.&lt;&#x2F;p&gt;
&lt;p&gt;On the surface, it&#x27;s easy to agree: it sounds sensible. In practice, it&#x27;s damn hard work.&lt;br &#x2F;&gt;
Every design decision turns into a deep dive through all the possible edge cases.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been recently struggling with it on one of my open source projects, and I feel like it could be useful to document
my design thought process—hence this blog post!&lt;br &#x2F;&gt;
I&#x27;ll walk you through the design problem I&#x27;m working on, showcasing the different challenges when trying to nail down
a &quot;good API&quot;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this post on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;programming&#x2F;comments&#x2F;17fyo2b&#x2F;crafting_boring_apis_lessons_learned_from&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#the-problem&quot;&gt;The problem&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#features-interact&quot;&gt;Features interact&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#route-nesting&quot;&gt;Route nesting&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#working-through-the-edge-cases&quot;&gt;Working through the edge cases&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#the-solution-space&quot;&gt;The solution space&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#documentation&quot;&gt;Documentation&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#enforced-partitioning&quot;&gt;Enforced partitioning&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#reject-ambiguity&quot;&gt;Reject ambiguity&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;api-design-should-be-boring&#x2F;#closing-thoughts&quot;&gt;Closing thoughts&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;the-problem&quot;&gt;The problem&lt;&#x2F;h1&gt;
&lt;p&gt;I need to add fallback handlers to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;, the Rust web framework I&#x27;m developing.&lt;br &#x2F;&gt;
A fallback handler is the function that will be invoked when an incoming request fails to match any of the routes you registered.
E.g. you receive a &lt;code&gt;GET &#x2F;users&lt;&#x2F;code&gt; but there is no &lt;code&gt;&#x2F;users&lt;&#x2F;code&gt; entry in your router.&lt;&#x2F;p&gt;
&lt;p&gt;Every framework (including Pavex) provides a default handler: most return a &lt;code&gt;404 Not Found&lt;&#x2F;code&gt; or a &lt;code&gt;405 Method Not Allowed&lt;&#x2F;code&gt; response.
405 if there is another route that uses the same path but expects a different method (e.g. &lt;code&gt;POST &#x2F;users&lt;&#x2F;code&gt;), 404 in all other cases.&lt;br &#x2F;&gt;
That default does not work for everyone. Your API might be serving a server-side rendered website: your fallback
should return a nice &quot;Page not found&quot; HTML page, ideally with some links to go back to the useful parts of your website.&lt;&#x2F;p&gt;
&lt;p&gt;You might have stricter requirements even if your API is designed to be consumed by other machines.&lt;br &#x2F;&gt;
For example, your API schema might require all errors to return a structured payload (e.g. following
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rfc-editor.org&#x2F;rfc&#x2F;rfc7807&quot;&gt;the problem details RFC&lt;&#x2F;a&gt;). An empty response is not OK.&lt;&#x2F;p&gt;
&lt;p&gt;Even worse (and the source of my current design dilemmas): you might be doing both things at once!&lt;br &#x2F;&gt;
All your &lt;code&gt;&#x2F;api&#x2F;*&lt;&#x2F;code&gt; routes expose a JSON-based API, but all your &lt;code&gt;&#x2F;web&#x2F;*&lt;&#x2F;code&gt; routes are serving HTML pages.
You need different fallbacks depending on the path of the incoming request.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;features-interact&quot;&gt;Features interact&lt;&#x2F;h1&gt;
&lt;p&gt;How do we accommodate these requirements?&lt;&#x2F;p&gt;
&lt;p&gt;The simplest option: add a &lt;code&gt;fallback&lt;&#x2F;code&gt; method to Pavex&#x27;s &lt;code&gt;Blueprint&lt;&#x2F;code&gt; API, our routing interface.
&lt;code&gt;fallback&lt;&#x2F;code&gt; would accept a request handler which would be automatically invoked whenever a request fails to route.&lt;br &#x2F;&gt;
Things get tricky when we consider the interaction with other features.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;route-nesting&quot;&gt;Route nesting&lt;&#x2F;h2&gt;
&lt;p&gt;Pavex allows you to break down your routing table into smaller ones.
It helps in keeping your application modular.&lt;&#x2F;p&gt;
&lt;p&gt;For example, you can have separate api and web modules: they are entirely self-contained and you delegate
to a top-level entrypoint the job of combining them.
This is done via nesting:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;nest_at&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;api&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, api_blueprint);
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;nest_at&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;web&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, web_blueprint);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It allows both sub-routers to ignore the fact that the other exists and that they need to &quot;distinguish&quot; themselves
via a path prefix. That knowledge is pushed to the parent module.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;bp&lt;&#x2F;code&gt;, &lt;code&gt;api_blueprint&lt;&#x2F;code&gt; and &lt;code&gt;web_blueprint&lt;&#x2F;code&gt; are all instances of the same type in Pavex: &lt;code&gt;Blueprint&lt;&#x2F;code&gt;, the routing interface.
If we add a &lt;code&gt;fallback&lt;&#x2F;code&gt; method to &lt;code&gt;Blueprint&lt;&#x2F;code&gt;, we are automatically allowing that method to be invoked by nested blueprints as well.
What does that mean?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;working-through-the-edge-cases&quot;&gt;Working through the edge cases&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s start from the easy case, our &lt;code&gt;&#x2F;api&#x2F;*&lt;&#x2F;code&gt; and &lt;code&gt;&#x2F;web&#x2F;*&lt;&#x2F;code&gt; nested routers.&lt;br &#x2F;&gt;
If a request path begins with &lt;code&gt;&#x2F;api&#x2F;&lt;&#x2F;code&gt; and it fails to match, what do we do?&lt;&#x2F;p&gt;
&lt;p&gt;Most obvious choice: invoke the fallback handler registered against &lt;code&gt;api_blueprint&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
If there isn&#x27;t one, invoke the one registered against the top-level blueprint.&lt;br &#x2F;&gt;
If there isn&#x27;t one, the framework default.&lt;&#x2F;p&gt;
&lt;p&gt;There is no ambiguity because the nesting pattern creates a &lt;em&gt;partition tree&lt;&#x2F;em&gt;. All routes with the same prefix belong to
the same sub-router.&lt;br &#x2F;&gt;
That, unfortunately, is not always the case.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s look at this other routing configuration as an example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;users&#x2F;:user_id&#x2F;reset&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, reset_handler);
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;nest_at&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;users&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, users_bp);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You are importing a blueprint from a third-party user-management library, but you want to extend its functionality with an extra route.&lt;&#x2F;p&gt;
&lt;p&gt;What happens when a &lt;code&gt;GET &#x2F;users&#x2F;12&#x2F;boom&lt;&#x2F;code&gt; request arrives?&lt;br &#x2F;&gt;
&lt;code&gt;users_bp&lt;&#x2F;code&gt; no longer contains all routes with a &lt;code&gt;&#x2F;users&#x2F;&lt;&#x2F;code&gt; prefix. Should the framework invoke the fallback defined
on the top-level blueprint? Or the one defined in the nested blueprint?&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s not that clear-cut!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-solution-space&quot;&gt;The solution space&lt;&#x2F;h1&gt;
&lt;p&gt;We could solve the problem in different ways:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;define a resolution mechanism and add it to Pavex&#x27;s documentation&lt;&#x2F;li&gt;
&lt;li&gt;enforce that nesting results into a partitioned routing table&lt;&#x2F;li&gt;
&lt;li&gt;reject fallback registrations that create routing ambiguity&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;&#x2F;h2&gt;
&lt;p&gt;Documentation is &quot;easy&quot;, but it doesn&#x27;t satisfy me.&lt;br &#x2F;&gt;
Users only reach for documentation if they are struggling to use the API or if they are trying to troubleshoot an issue.&lt;&#x2F;p&gt;
&lt;p&gt;Routing looks easy and familiar, you don&#x27;t need to check the docs for it (beyond a few examples).&lt;&#x2F;p&gt;
&lt;p&gt;If you ran into ambiguity and that causes a bug, we&#x27;ve already caused you an issue. The documentation doesn&#x27;t help in &lt;em&gt;preventing it&lt;&#x2F;em&gt;.
It becomes an excuse for the framework, something to point at to say &quot;you should have known better&quot;. Not great.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;enforced-partitioning&quot;&gt;Enforced partitioning&lt;&#x2F;h2&gt;
&lt;p&gt;What about enforcing a partitioned tree?&lt;br &#x2F;&gt;
That &lt;em&gt;could&lt;&#x2F;em&gt; work, but it reduces the expressiveness of our routing API. The extension pattern I showed before would no longer be allowed.&lt;&#x2F;p&gt;
&lt;p&gt;What for? To have non-ambiguous fallback semantics in a few uncommon scenarios.&lt;&#x2F;p&gt;
&lt;p&gt;It violates another one of my favourite API design principles: simple things should be simple, complex things should be possible.
We should avoid complicating everyone&#x27;s life in order to accommodate a niche corner case.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reject-ambiguity&quot;&gt;Reject ambiguity&lt;&#x2F;h2&gt;
&lt;p&gt;That leaves us with option 3: rejecting routers where we have fallback ambiguity.&lt;&#x2F;p&gt;
&lt;p&gt;We expect few people to run into this issue, therefore we are minimising the amount of discomfort for the average user.
Since Pavex is built on top of a transpiler, we can also amortize the worst case scenario: we can reject the router at
compile-time with a good error message, rather than failing at runtime when the application is initialised.&lt;br &#x2F;&gt;
We can explain to the user where they should attach their fallback in order to remove the ambiguity, as well as
showcase how they could rework their routing to be more partition-friendly.&lt;&#x2F;p&gt;
&lt;p&gt;It sounds like the most sensible option!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;&#x2F;h1&gt;
&lt;p&gt;There&#x27;s another element at play that we haven&#x27;t discussed: implementation complexity.&lt;br &#x2F;&gt;
Determining &lt;em&gt;if&lt;&#x2F;em&gt; we have fallback ambiguity is tricky. Which leads us to the last design principle of this
journey: whenever a few can take on the pain of many, they should.&lt;&#x2F;p&gt;
&lt;p&gt;I hope this was insightful, and I want to close with an invitation: have &lt;em&gt;you&lt;&#x2F;em&gt; been working through a design puzzle?
Why don&#x27;t you share your thought process, if the project is out in the open&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#rfc&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;?&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this post on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;programming&#x2F;comments&#x2F;17fyo2b&#x2F;crafting_boring_apis_lessons_learned_from&#x2F;&quot;&gt;Reddit&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;rfc&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Yes, that&#x27;s what RFCs are for! But most projects are not big enough to need them, which leaves a lot of interesting design dilemma undocumented.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</description>
      </item>
      <item>
          <title>Pavex DevLog #6: designing safe and ergonomic middlewares</title>
          <pubDate>Fri, 01 Sep 2023 10:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-06/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-06/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-06/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is progress report about &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;, a new Rust web framework that I have been working on. It is currently
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;early stages of development&lt;&#x2F;a&gt;, working towards its first alpha release.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; to learn more about the vision!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;It&#x27;s time for another progress report on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;, covering what has been done in July and August!&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been hard at work with one objective: adding middleware support to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
It&#x27;s far from polished at this point, but it (finally) works 🚀&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll use this report as a chance to deep-dive on a few topics:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Why middleware support is a key requirement for production-readiness&lt;&#x2F;li&gt;
&lt;li&gt;The challenges of designing a middleware system for a Rust web framework&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;&#x27;s middleware design&lt;&#x2F;li&gt;
&lt;li&gt;The limitations of our current implementation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;But if you&#x27;re short on time, here&#x27;s a simple timeout middleware to showcase what a middleware looks like in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::{middleware::Next, response::Response};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::future::IntoFuture;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;tokio::time::{timeout, error::Elapsed};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;timeout_middleware&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;C&amp;gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; A handle on the rest of the processing pipeline for the incoming
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; request. 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Middlewares can choose to short-circuit the execution (i.e. 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; return an error and abort the processing) or perform some 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; computation and then delegate to `next` by awaiting it.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; All middlewares in Pavex *must* take `Next&amp;lt;_&amp;gt;` as an input.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next&amp;lt;C&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Middlewares can take advantage of dependency injection! 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; You just list what inputs you need and the framework will provide them
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; (if possible, or return a nice error at *compile-time* if not).
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `TimeoutConfig` could be defined at start-up time, sharing the same 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; value for all routes, or it could be customised at runtime on a 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; per-request basis (e.g. to provide a configurable quality of service
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; depending on the pricing plan of the client issueing the request).
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `timeout_middleware` doesn&amp;#39;t care how or when `TimeoutConfig`
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; gets computed. 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Happy decoupling!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;config&lt;&#x2F;span&gt;&lt;span&gt;: TimeoutConfig
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Response, Elapsed&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;    C: IntoFuture&amp;lt;Output = Response&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span&gt;(config.request_timeout, next.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into_future&lt;&#x2F;span&gt;&lt;span&gt;()).await
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can then add this middleware to your request chain by calling &lt;code&gt;Blueprint::wrap&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::{blueprint::Blueprint, f};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;api&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;wrap&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::timeout_middleware));
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pretty straight-forward, isn&#x27;t it?&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;167wfok&#x2F;designing_safe_and_ergonomic_middlewares_pavex&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#middlewares&quot;&gt;Middlewares&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#why-do-we-need-middlewares&quot;&gt;Why do we need middlewares?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#the-shape-of-a-middleware&quot;&gt;The shape of a middleware&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#lifecycle-hooks&quot;&gt;Lifecycle hooks&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#pipeline-wrappers&quot;&gt;Pipeline wrappers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#hooks-have-no-futures&quot;&gt;Hooks have no &lt;code&gt;Future&lt;&#x2F;code&gt;s&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#interface-design&quot;&gt;Interface design&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#overhead&quot;&gt;Overhead&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#generics&quot;&gt;Generics&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#state&quot;&gt;State&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#pavex-s-design&quot;&gt;Pavex&#x27;s design&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#look-ma-no-traits&quot;&gt;Look ma, no traits!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#explicit-state-dependencies&quot;&gt;Explicit state dependencies&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#concrete-types-over-generics&quot;&gt;Concrete types over generics&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-06&#x2F;#what-s-next&quot;&gt;What&#x27;s next&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;middlewares&quot;&gt;Middlewares&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;why-do-we-need-middlewares&quot;&gt;Why do we need middlewares?&lt;&#x2F;h2&gt;
&lt;p&gt;Every successful project grows in complexity over time.&lt;br &#x2F;&gt;
What started out as a couple of API endpoints often evolves into a large application, with tens if not hundreds of request handlers.&lt;&#x2F;p&gt;
&lt;p&gt;While the business logic may vary wildly from one request handler to the next, some concerns are usually shared. To mention a few common examples:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Enforcing a &lt;strong&gt;timeout&lt;&#x2F;strong&gt; on the processing of all incoming requests.&lt;br &#x2F;&gt;
We don&#x27;t want to waste server resources on requests that the caller has already given up on. It also protects us from a certain type of denial of service attacks.&lt;&#x2F;li&gt;
&lt;li&gt;Ensuring that the caller is &lt;strong&gt;authenticated&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Authorization logic might be deeply entertwined with the business logic, but we usually deploy the same kind of authentication (e.g. API tokens, request signing) across all sensitive endpoints exposed by an API.&lt;&#x2F;li&gt;
&lt;li&gt;Emitting &lt;strong&gt;telemetry&lt;&#x2F;strong&gt; data.&lt;br &#x2F;&gt;
There should be at least one log record for each incoming request, capturing some basic HTTP-level information (e.g. path, method, response status code, etc.) on top of some business-level attributes you might care about. You might also have metrics that need to be updated for every request (e.g. to track error rate!).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The challenge is twofold:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;We don&#x27;t want to duplicate this logic in each of our request handlers.&lt;&#x2F;li&gt;
&lt;li&gt;We want to make sure that we are using the &lt;strong&gt;same logic&lt;&#x2F;strong&gt; for all routes.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;You could satisfy 1. by extracting the common logic in a function and invoking it in all your request handlers. But that won&#x27;t cut it for 2.: every time you&#x27;re adding a new request handler, you need to &lt;strong&gt;actively remember&lt;&#x2F;strong&gt; to add those invocations.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the challenge that the middleware pattern tries to solve.
We no longer deal with those concerns &lt;strong&gt;inside&lt;&#x2F;strong&gt; the bodies of our request handlers. We invoke the relevant logic either &lt;strong&gt;before&lt;&#x2F;strong&gt; or &lt;strong&gt;after&lt;&#x2F;strong&gt; the request handlers kick in.&lt;br &#x2F;&gt;
All middlewares are declared once, centrally, and that&#x27;s the only place we need to look at if we want to make changes. Every time a new route is added, it is automatically covered by the existing middlewares for that set of paths.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-shape-of-a-middleware&quot;&gt;The shape of a middleware&lt;&#x2F;h2&gt;
&lt;p&gt;What do middlewares look like, in pratice?&lt;br &#x2F;&gt;
The details vary depending on the programming language and the specific framework you&#x27;re looking at, but middleware interfaces broadly fall into two categories:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Lifecycle hooks&lt;&#x2F;li&gt;
&lt;li&gt;Pipeline wrappers&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;When it comes to Rust, &lt;strong&gt;pipeline wrappers are fundamentally more capable&lt;&#x2F;strong&gt; than lifecycle hooks. I&#x27;ll quickly show you the two interfaces and explain why.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;lifecycle-hooks&quot;&gt;Lifecycle hooks&lt;&#x2F;h3&gt;
&lt;p&gt;You register a function or a method with the framework, asking for it to be invoked at a specific point in the request lifecycle—e.g. before the request handler is invoked, or after. These are often referred to as &quot;callbacks&quot; in some ecosystems.&lt;&#x2F;p&gt;
&lt;p&gt;The details may vary depending on the framework, but a reference interface would look somewhat like this in Rust:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;trait &lt;&#x2F;span&gt;&lt;span&gt;IncomingRequestHook {
&lt;&#x2F;span&gt;&lt;span&gt;    async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;invoke&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; Request) -&amp;gt; Outcome;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;Outcome {
&lt;&#x2F;span&gt;&lt;span&gt;    ContinueProcessing,
&lt;&#x2F;span&gt;&lt;span&gt;    EarlyReturn(Response),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;ve ignored the fallibility angle for simplicity.&lt;&#x2F;p&gt;
&lt;p&gt;The key detail: the &lt;code&gt;invoke&lt;&#x2F;code&gt; method has no way to &lt;strong&gt;directly&lt;&#x2F;strong&gt; manipulate (or invoke) the rest of the processing pipeline (i.e. later middlewares or the request handler itself). It makes the decision, but defers the execution to the framework machinery.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;pipeline-wrappers&quot;&gt;Pipeline wrappers&lt;&#x2F;h3&gt;
&lt;p&gt;Pipeline wrappers are the exact opposite: they &lt;strong&gt;wrap&lt;&#x2F;strong&gt; around the remaining part of the processing pipeline, and they are fully in control of its invocation (or lack thereof).&lt;br &#x2F;&gt;
In pseudo-code, ignoring fallibility and other details again:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;trait &lt;&#x2F;span&gt;&lt;span&gt;WrappingMiddleware {
&lt;&#x2F;span&gt;&lt;span&gt;    async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;wrap&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: Request, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next) -&amp;gt; Response;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;hooks-have-no-futures&quot;&gt;Hooks have no &lt;code&gt;Future&lt;&#x2F;code&gt;s&lt;&#x2F;h3&gt;
&lt;p&gt;How do you go about implementing a timeout middleware? Or a logging middleware that keeps track of the actual processing time spent executing your business logic?&lt;&#x2F;p&gt;
&lt;p&gt;You necessarily need access to the &lt;strong&gt;&lt;code&gt;Future&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; that represents the computation that you want to &lt;strong&gt;decorate&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
If you want to invoke &lt;code&gt;tokio::time::timeout&lt;&#x2F;code&gt; on your processing pipeline, you need to pass it as input the &lt;code&gt;Future&lt;&#x2F;code&gt; that&#x27;s going to drive that processing pipeline to completion:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span&gt;(timeout_duration, next_future).await
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can&#x27;t implement the same logic using a lifecycle hook, since it doesn&#x27;t get access to that &lt;code&gt;Future&lt;&#x2F;code&gt; type.&lt;br &#x2F;&gt;
The same applies to logging: to attach a &lt;code&gt;tracing&lt;&#x2F;code&gt; &lt;code&gt;Span&lt;&#x2F;code&gt; to your pipeline, you need to be able to call &lt;code&gt;instrument&lt;&#x2F;code&gt; on its &lt;code&gt;Future&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;next_future.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(my_span).await
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Lifecycle hooks fall short once again.&lt;&#x2F;p&gt;
&lt;p&gt;As a framework author, it leaves you with two options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Lift all the functionality that requires access to that &lt;code&gt;Future&lt;&#x2F;code&gt; into the framework&lt;&#x2F;li&gt;
&lt;li&gt;Empower users to write their own solutions, using wrapping-style middlewares&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you look at the ecosystem, you find wrapping-style middlewares in all major Rust frameworks:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Transform&lt;&#x2F;code&gt; and &lt;code&gt;Service&lt;&#x2F;code&gt; in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;actix.rs&#x2F;docs&#x2F;middleware&quot;&gt;&lt;code&gt;actix-web&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Layer&lt;&#x2F;code&gt; and &lt;code&gt;Service&lt;&#x2F;code&gt; in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;latest&#x2F;axum&#x2F;middleware&#x2F;index.html&quot;&gt;&lt;code&gt;axum&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Middleware&lt;&#x2F;code&gt; and &lt;code&gt;Endpoint&lt;&#x2F;code&gt; in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;poem&#x2F;latest&#x2F;poem&#x2F;middleware&#x2F;trait.Middleware.html&quot;&gt;&lt;code&gt;poem&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;rocket&lt;&#x2F;code&gt; is the only exception, using the callback-style with its &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rocket.rs&#x2F;v0.5-rc&#x2F;guide&#x2F;fairings&#x2F;#overview&quot;&gt;&lt;code&gt;Fairing&lt;&#x2F;code&gt;s&lt;&#x2F;a&gt;. As a consequence, both logging and timeouts have to be provided as framework built-ins. As far as I could see, there is no way to customize them or bring your own.&lt;&#x2F;p&gt;
&lt;p&gt;What about &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;?&lt;br &#x2F;&gt;
The goal is to build a batteries-included framework, providing most of the functionality you need to build a production-ready application as first-party code. At the same time, you shouldn&#x27;t be &lt;em&gt;forced&lt;&#x2F;em&gt; to use our solutions. Depending on your requirements, they might be inadequate.&lt;br &#x2F;&gt;
Wrapping-style is the way forward for &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;: you&#x27;ll be able to use our solutions, but chip in with your own if needed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;interface-design&quot;&gt;Interface design&lt;&#x2F;h2&gt;
&lt;p&gt;We have settled on an overall approach, but the devil is in the details. What does the middleware interface &lt;em&gt;actually&lt;&#x2F;em&gt; look like?&lt;&#x2F;p&gt;
&lt;p&gt;You have to make three consequential choices:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Overhead. Does the interface need to be a zero-cost abstraction?&lt;&#x2F;li&gt;
&lt;li&gt;Generics. How many generic parameters are too many?&lt;&#x2F;li&gt;
&lt;li&gt;State. How does the middleware access shared state?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;overhead&quot;&gt;Overhead&lt;&#x2F;h3&gt;
&lt;p&gt;Rust&#x27;s async story is still maturing. As of today, we lack first-class support for &lt;code&gt;async&lt;&#x2F;code&gt; functions in traits.&lt;br &#x2F;&gt;
You need to fully spell it out to the compiler: you have write to a synchronous function that returns a type that implements the &lt;code&gt;Future&lt;&#x2F;code&gt; trait.&lt;&#x2F;p&gt;
&lt;p&gt;You can see what this looks like in &lt;code&gt;tower&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;Service&lt;&#x2F;code&gt; trait definition:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;Service&amp;lt;Request&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Response;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Error;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Future: Future
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;::Future as Future&amp;gt;::Output == Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Response, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Error&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Future;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A long-winded way to write &lt;code&gt;async fn call (&amp;amp;mut self, req: Request) -&amp;gt; Result&amp;lt;Self::Response, Self::Error&amp;gt;&lt;&#x2F;code&gt;, the best you can do today in stable Rust if you don&#x27;t want to compromise on performance.&lt;br &#x2F;&gt;
As an alternative, you could use the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;async-trait&#x2F;latest&#x2F;async_trait&#x2F;&quot;&gt;&lt;code&gt;async_trait&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate: its &lt;code&gt;#[async_trait]&lt;&#x2F;code&gt; lets you write &quot;async&quot; function in traits today:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;async_trait&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;async_trait&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;Service&amp;lt;Request&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Response;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Error;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Response, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Error&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;☝️ works in stable Rust, today. But it comes at a cost—it automatically adds a layer of indirection, since it automatically &lt;code&gt;Box&lt;&#x2F;code&gt;es the async body of your &lt;code&gt;call&lt;&#x2F;code&gt; implementation.&lt;br &#x2F;&gt;
That indirection has a performance impact—negligible in most cases, but nonetheless there. The most popular Rust web frameworks strive to have no overhead whatsoever, therefore they shy away from solutions that involve auto-boxing. They might offer it as a &quot;simplified&quot; interface, &lt;em&gt;alongside&lt;&#x2F;em&gt; the zero-overhead one. See &lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;latest&#x2F;axum&#x2F;middleware&#x2F;fn.from_fn.html&quot;&gt;&lt;code&gt;from_fn&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; or &lt;code&gt;actix-web&lt;&#x2F;code&gt;&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web-lab&#x2F;latest&#x2F;actix_web_lab&#x2F;middleware&#x2F;fn.from_fn.html&quot;&gt;&lt;code&gt;from_fn&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s another constraint though: the &lt;code&gt;Future&lt;&#x2F;code&gt; returned by your method must be &lt;em&gt;nameable&lt;&#x2F;em&gt;. What&#x27;s the name of the future returned by:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: Request) -&amp;gt; Response {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It isn&#x27;t &lt;code&gt;Response&lt;&#x2F;code&gt;. We can say, at best, &lt;code&gt;impl Future&amp;lt;Output=Response&amp;gt;&lt;&#x2F;code&gt;, but you can&#x27;t use that as an associated type in a trait implementation! This restriction will eventually be lifted as well (see &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rust-lang.github.io&#x2F;impl-trait-initiative&#x2F;RFCs&#x2F;rpit-in-traits.html&quot;&gt;this RFC&lt;&#x2F;a&gt;), but it often forces you to box &lt;em&gt;anyway&lt;&#x2F;em&gt; even if you&#x27;re implementing the zero-overhead version of an async trait.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;generics&quot;&gt;Generics&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s assume that &lt;code&gt;async&lt;&#x2F;code&gt; functions in traits have been shipped and we can enjoy a &quot;slimmer&quot; version of &lt;code&gt;tower&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;Service&lt;&#x2F;code&gt; trait:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;Service&amp;lt;Request&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Response;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Error;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Request, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Error&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For each middleware implementation, we need to make three choice:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;What bounds should we enforce on the generic &lt;code&gt;Request&lt;&#x2F;code&gt; parameter?&lt;&#x2F;li&gt;
&lt;li&gt;What should our &lt;code&gt;Response&lt;&#x2F;code&gt; type be?&lt;&#x2F;li&gt;
&lt;li&gt;What should our &lt;code&gt;Error&lt;&#x2F;code&gt; type be?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s compare it with the &lt;code&gt;Middleware&lt;&#x2F;code&gt; trait from &lt;code&gt;tide&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;Middleware&amp;lt;State&amp;gt;: Send + Sync + &amp;#39;static {
&lt;&#x2F;span&gt;&lt;span&gt;    async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: Request&amp;lt;State&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next&amp;lt;&amp;#39;_, State&amp;gt;) -&amp;gt; Result&amp;lt;Response, Error&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ignore the &lt;code&gt;State&lt;&#x2F;code&gt; parameter, we&#x27;ll get to state management in the next section.&lt;br &#x2F;&gt;
Apart from &lt;code&gt;State&lt;&#x2F;code&gt;, there are no generics: you take a concrete &lt;code&gt;Request&lt;&#x2F;code&gt; type as input and are expected to return a concrete &lt;code&gt;Response&lt;&#x2F;code&gt; type or a concrete &lt;code&gt;Error&lt;&#x2F;code&gt; type—all defined by the framework.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s definitely more intuitive: you can just focus on the actual middleware logic rather than fidgeting around with trait bounds. Ergonomicity comes at a cost though: in order to use a concrete &lt;code&gt;Response&lt;&#x2F;code&gt; type, &lt;code&gt;tide&lt;&#x2F;code&gt; forces you to &lt;code&gt;Box&lt;&#x2F;code&gt; the body of your response.&lt;&#x2F;p&gt;
&lt;p&gt;Tradeoffs, tradeoffs everywhere.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;state&quot;&gt;State&lt;&#x2F;h3&gt;
&lt;p&gt;Last but not least: state!&lt;br &#x2F;&gt;
There are two types of state in a request processing pipeline:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Application state&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
It&#x27;s usually built before starting the application and it&#x27;s long-lived: it stays around until the application shuts down.&lt;br &#x2F;&gt;
Example: a database connection pool.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Request-scoped state&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
It is computed on a per-request basis and it&#x27;s used by multiple steps of the request processing pipeline.&lt;br &#x2F;&gt;
Example: the session state for a logged-in user, extracted in a middleware and later used in the request handler itself.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;axum&lt;&#x2F;code&gt; and &lt;code&gt;tide&lt;&#x2F;code&gt; follow the same approach when it comes to application state: it becomes a generic parameter on the overall application type. They lean on the compiler to make sure that middlewares and request handlers expect the &quot;right type.&quot; It comes with a downside: you need to have a global type to represent the entirety of the state. Not too bad, if you ask my opinion.&lt;&#x2F;p&gt;
&lt;p&gt;Request-scoped state is another story though.&lt;br &#x2F;&gt;
Most (all?) major Rust web frameworks throw compile-time checks out of the window and rely on a request-scoped type map (often called &quot;request extensions&quot;) for managing request-scoped state. At a glance, it looks like this: &lt;code&gt;HashMap&amp;lt;TypeId, Box&amp;lt;dyn Any + [...]&amp;gt;, [...]&amp;gt;&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
When you build a piece of request-local state that you want to pass down (or up) the processing chain, you insert it into the map.&lt;br &#x2F;&gt;
When you need a piece of request-local state that you haven&#x27;t built yourself, you get it from the map—and hope really hard that it&#x27;s there.&lt;&#x2F;p&gt;
&lt;p&gt;Request extensions are the bane of my existence when working on backend applications in Rust: they are the most common (and annoying) source of bugs when trying to add a new middleware or updating your existing stack. The coupling they create is hidden to the compiler—you only find out at runtime if you misconfigured something.&lt;&#x2F;p&gt;
&lt;p&gt;You need to carefully read the docs of each middleware you are planning to use in order to make sure that you have installed (and applied in the correct order) any other middleware they might depend on.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m strongly determined to have no untyped side-channel for state in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pavex-s-design&quot;&gt;Pavex&#x27;s design&lt;&#x2F;h2&gt;
&lt;p&gt;At this stage, I hope I convinced you that there are no easy choices when it comes to designing a middleware interface.&lt;br &#x2F;&gt;
But enough with the excuses—I&#x27;ll walk you through the design I landed on for &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;look-ma-no-traits&quot;&gt;Look ma, no traits!&lt;&#x2F;h3&gt;
&lt;p&gt;We side-step the trait problem entirely: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; relies on &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;#a-peek-under-the-hood&quot;&gt;its own compile-time reflection engine&lt;&#x2F;a&gt; to determine what fits or doesn&#x27;t fit its interfaces.&lt;br &#x2F;&gt;
These are the requirements:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The middleware is an asynchronous function (or method).&lt;&#x2F;li&gt;
&lt;li&gt;It takes as input &lt;code&gt;Next&amp;lt;C&amp;gt;&lt;&#x2F;code&gt;, a handle on the request pipeline it&#x27;s wrapping.&lt;&#x2F;li&gt;
&lt;li&gt;It either returns a &lt;code&gt;pavex::Response&lt;&#x2F;code&gt; or a &lt;code&gt;Result&amp;lt;pavex::Response, E&amp;gt;&lt;&#x2F;code&gt;, where &lt;code&gt;E&lt;&#x2F;code&gt; is an error type.&lt;&#x2F;li&gt;
&lt;li&gt;It can take additional input parameters, as long as you register a constructor for them.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Going back to our timeout example, this is what a timeout middleware looks like in Pavex:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::{middleware::Next, response::Response};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::future::IntoFuture;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;tokio::time::{timeout, error::Elapsed};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;timeout_middleware&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;C&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;next&lt;&#x2F;span&gt;&lt;span&gt;: Next&amp;lt;C&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;config&lt;&#x2F;span&gt;&lt;span&gt;: TimeoutConfig) -&amp;gt; Result&amp;lt;Response, Elapsed&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;    C: IntoFuture&amp;lt;Output = Response&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span&gt;(config.request_timeout, next.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into_future&lt;&#x2F;span&gt;&lt;span&gt;()).await
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;explicit-state-dependencies&quot;&gt;Explicit state dependencies&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s zoom in on &lt;code&gt;TimeoutConfig&lt;&#x2F;code&gt;, our second input parameter: is it request-scoped? Or is it part of the application state?&lt;br &#x2F;&gt;
It doesn&#x27;t matter for &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt;: all dependencies on a state (no matter the lifecycle) &lt;strong&gt;must&lt;&#x2F;strong&gt; be encoded in the signature of either middlewares, request handlers or other constructors.&lt;br &#x2F;&gt;
If you forget to register a constructor for a piece of state, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; will remind you about it at compile-time:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × I can&amp;#39;t invoke your wrapping middleware, `timeout`, because it needs an instance of
&lt;&#x2F;span&gt;&lt;span&gt;  │ `TimeoutConfig` as input, but I can&amp;#39;t find a constructor for that type.
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;blueprint.rs:18:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  18 │
&lt;&#x2F;span&gt;&lt;span&gt;  │  19 │     bp.wrap(f!(crate::timeout));
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·             ────────┬────────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                     ╰── The wrapping middleware was registered here
&lt;&#x2F;span&gt;&lt;span&gt;  │  20 │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │    ╭─[src&#x2F;load_shedding.rs:5:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  5 │
&lt;&#x2F;span&gt;&lt;span&gt;  │  6 │ pub async fn timeout&amp;lt;T&amp;gt;(next: Next&amp;lt;T&amp;gt;, timeout_config: TimeoutConfig) -&amp;gt; Response
&lt;&#x2F;span&gt;&lt;span&gt;  │    ·                                                        ──────┬──────
&lt;&#x2F;span&gt;&lt;span&gt;  │    ·                I don&amp;#39;t know how to construct an instance of this input parameter
&lt;&#x2F;span&gt;&lt;span&gt;  │    ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: Register a constructor for `TimeoutConfig`
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Fixing the error is easy enough:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::{
&lt;&#x2F;span&gt;&lt;span&gt;    blueprint::{Blueprint, constructor::Lifecycle}, 
&lt;&#x2F;span&gt;&lt;span&gt;    f
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;api&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Register a constructor for `TimeoutConfig` and declare
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; it is going to live as long as the application itself,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; a singleton!
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::TimeoutConfig::from_config),
&lt;&#x2F;span&gt;&lt;span&gt;        Lifecycle::Singleton,
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you prefer to determine the timeout on a per-request basis, you can register a constructor with a &lt;code&gt;RequestScoped&lt;&#x2F;code&gt; lifecycle instead:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::{
&lt;&#x2F;span&gt;&lt;span&gt;    blueprint::{Blueprint, constructor::Lifecycle}, 
&lt;&#x2F;span&gt;&lt;span&gt;    f
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;api&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Register a constructor for `TimeoutConfig` and declare
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; it is going to be scoped to a request lifecycle.
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::TimeoutConfig::from_request),
&lt;&#x2F;span&gt;&lt;span&gt;        Lifecycle::RequestScoped,
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This design isn&#x27;t without downsides: you can&#x27;t &quot;create&quot; state from inside a middleware and then share it with a downstream step of the processing pipeline.&lt;br &#x2F;&gt;
You are forced to extract the creation of that state into a separate constructor with a &lt;code&gt;RequestScoped&lt;&#x2F;code&gt; lifecycle and let the framework inject it as an input parameter in both locations. I believe it&#x27;s going to prove a useful forcing function towards isolated components with well-defined responsibilities, but we&#x27;ll only know when &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; is taken out for a spin by a few users.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;concrete-types-over-generics&quot;&gt;Concrete types over generics&lt;&#x2F;h3&gt;
&lt;p&gt;The eagle-eyed among you will have noticed our last design choice along the axes we examined: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; uses a concrete response type, &lt;code&gt;pavex::response::Response&lt;&#x2F;code&gt;. As discussed previously, this introduces a level of indirection—the response body is always boxed.&lt;br &#x2F;&gt;
The type of applications I&#x27;m designing for in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; (backend-for-the-frontend, enterprise microservices, monoliths) rarely suffer from performance issues due to an extra level of pointer indirection: you need to look at data modelling, database access patterns, caching, etc.&lt;br &#x2F;&gt;
On this topic, I lean towards getting a productivity boost rather than raising the performance ceiling by a millimeter.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-next&quot;&gt;What&#x27;s next&lt;&#x2F;h1&gt;
&lt;p&gt;The current middleware implementation is definitely an MVP. In particular:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;We currently box the &lt;code&gt;Future&lt;&#x2F;code&gt; that represents the rest of the request processing pipeline in &lt;code&gt;Next&amp;lt;_&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;The concrete type we generate for &lt;code&gt;C&lt;&#x2F;code&gt; in &lt;code&gt;Next&amp;lt;C&amp;gt;&lt;&#x2F;code&gt; (one for each route+middleware pair) can&#x27;t take any lifetime parameter at the moment. This prevents you from working with state that borrows from the incoming request or the long-lived application state.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Both of these are limitations of the current implementation: they made it easier to get to a working prototype, but they aren&#x27;t fundamental design defects. I&#x27;ll be improving the code generator to remove these issues over the coming month.&lt;&#x2F;p&gt;
&lt;p&gt;Once 2. is solved though, we&#x27;ll hit another problem: you can&#x27;t borrow non-static data across an &lt;code&gt;await&lt;&#x2F;code&gt; point if you&#x27;re using &lt;code&gt;hyper&lt;&#x2F;code&gt; in its &quot;default&quot; configuration (i.e. work-stealing multi-threaded HTTP server). I&#x27;ll be moving &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; towards a thread-per-core design to lift this restriction—I&#x27;ll explain the rationale further in the future.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s all for August, see you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;167wfok&#x2F;designing_safe_and_ergonomic_middlewares_pavex&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;Pavex&lt;&#x2F;a&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;em&gt;Thanks to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;benw.is&#x2F;&quot;&gt;Ben&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;conradludgate.com&#x2F;&quot;&gt;Conrad&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;predr.ag&#x2F;&quot;&gt;Predrag&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sponsors&#x2F;robjtede&quot;&gt;Rob&lt;&#x2F;a&gt; for reviewing the draft of this article.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-09-01T10:00:10.47Z&quot;&gt;September 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-05&amp;#x2F;&quot;&gt;Pavex Report #5&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-is-in-closed-beta&amp;#x2F;&quot;&gt;Pavex is in closed beta&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Pavex DevLog #5: redesigning our runtime types</title>
          <pubDate>Thu, 29 Jun 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-05/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-05/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-05/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is progress report about &lt;code&gt;pavex&lt;&#x2F;code&gt;, a new Rust web framework that I have been working on. It is currently
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;early stages of development&lt;&#x2F;a&gt;, working towards its first alpha release.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; to learn more about the vision!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;It&#x27;s time for another progress report on Pavex, covering the work done in June!&lt;&#x2F;p&gt;
&lt;p&gt;At a glance:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Pavex&#x27;s runtime abstractions are taking shape. We have our own &lt;code&gt;RequestHead&lt;&#x2F;code&gt; and &lt;code&gt;Response&lt;&#x2F;code&gt; types, more extractors and better machinery for working with &lt;code&gt;Response&lt;&#x2F;code&gt; bodies (e.g. the &lt;code&gt;TypedBody&lt;&#x2F;code&gt; trait)&lt;&#x2F;li&gt;
&lt;li&gt;I&#x27;ve consolidated the user-facing crates. You no longer have &lt;code&gt;pavex_runtime&lt;&#x2F;code&gt; and &lt;code&gt;pavex_builder&lt;&#x2F;code&gt; as separate dependencies: you just depend on &lt;code&gt;pavex&lt;&#x2F;code&gt; to get everything you need.&lt;&#x2F;li&gt;
&lt;li&gt;Dogfooding the framework (i.e. trying to build something with it) is paying dividends. I&#x27;ve identified a variety of bugs and shortcomings; some have been fixed already, others are planned for the future.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s dive in!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;14m60i9&#x2F;pavex_devlog_5_redesigning_our_runtime_types_rust&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#runtime-evolution&quot;&gt;Runtime evolution&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#no-request-for-pavex&quot;&gt;No &lt;code&gt;Request&lt;&#x2F;code&gt; for Pavex&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#our-own-response-type&quot;&gt;Our own &lt;code&gt;Response&lt;&#x2F;code&gt; type&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#extensions-considered-harmful&quot;&gt;Extensions considered harmful&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#api-control&quot;&gt;API control&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#preserving-interoperability&quot;&gt;Preserving interoperability&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#typedbody-and-intoresponse&quot;&gt;&lt;code&gt;TypedBody&lt;&#x2F;code&gt; and &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#one-pavex-crate-to-rule-them-all&quot;&gt;One &lt;code&gt;pavex&lt;&#x2F;code&gt; crate to rule them all&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#the-realworld-specification&quot;&gt;The Realworld specification&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-05&#x2F;#what-s-next&quot;&gt;What&#x27;s next&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;runtime-evolution&quot;&gt;Runtime evolution&lt;&#x2F;h2&gt;
&lt;p&gt;The runtime side of Pavex was barely stubbed last month—we were directly re-exporting the entirety of &lt;code&gt;http&lt;&#x2F;code&gt; and &lt;code&gt;hyper&lt;&#x2F;code&gt;, with litte to no ceremony.&lt;br &#x2F;&gt;
As I started to implement the Realworld specification using Pavex (more on that later), I refined the runtime abstractions in lockstep.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll give you a quick overview of the changes, but keep in mind that things are very fluid at the moment!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;no-request-for-pavex&quot;&gt;No &lt;code&gt;Request&lt;&#x2F;code&gt; for Pavex&lt;&#x2F;h3&gt;
&lt;p&gt;The first change is that Pavex no longer exposes the &lt;code&gt;Request&lt;&#x2F;code&gt; type from &lt;code&gt;http&lt;&#x2F;code&gt;. Even more radical: Pavex doesn&#x27;t have a &lt;code&gt;Request&lt;&#x2F;code&gt; type at all!&lt;&#x2F;p&gt;
&lt;p&gt;You can think of an HTTP request as a combination of two parts:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The request &lt;em&gt;head&lt;&#x2F;em&gt;, which contains the HTTP version (e.g. &lt;code&gt;1.1&lt;&#x2F;code&gt;), the path (e.g. &lt;code&gt;&#x2F;users&lt;&#x2F;code&gt;), the method (e.g. &lt;code&gt;POST&lt;&#x2F;code&gt; or &lt;code&gt;GET&lt;&#x2F;code&gt;) and the headers.&lt;&#x2F;li&gt;
&lt;li&gt;The request &lt;em&gt;body&lt;&#x2F;em&gt;, which contains the payload of the request (e.g. a JSON document or a protobuf-encoded one).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Many extractors (e.g. &lt;code&gt;RouteParams&lt;&#x2F;code&gt;, &lt;code&gt;QueryParams&lt;&#x2F;code&gt;) only need to look at the request head to do their job.&lt;br &#x2F;&gt;
But convenience is king: most folks (based on my personal experience) default to using the &lt;code&gt;Request&lt;&#x2F;code&gt; type as input to their request handlers and extractors—even if they have no use for the body.&lt;&#x2F;p&gt;
&lt;p&gt;This becomes a problem in the context of Rust&#x27;s borrow checker. &lt;strong&gt;Bundling together data with different usage patterns is a recipe for pain and frustration&lt;&#x2F;strong&gt; when working with Rust.&lt;&#x2F;p&gt;
&lt;p&gt;The request body is streamed from the client. In order to process that stream (e.g. buffer it in memory), you need to hold a mutable reference to it—i.e. an &lt;strong&gt;exclusive&lt;&#x2F;strong&gt; reference (i.e. &lt;code&gt;&amp;amp;mut Body&lt;&#x2F;code&gt; or &lt;code&gt;Body&lt;&#x2F;code&gt;).&lt;br &#x2F;&gt;
Most operations involving the request head, instead, work with a &lt;strong&gt;shared&lt;&#x2F;strong&gt; reference (i.e. &lt;code&gt;&amp;amp;RequestHead&lt;&#x2F;code&gt;). There is no mutation involved.&lt;&#x2F;p&gt;
&lt;p&gt;If you have a &quot;bundled&quot; &lt;code&gt;Request&lt;&#x2F;code&gt; type, these access patterns clash. You can&#x27;t have an exclusive reference to the request body while you&#x27;re holding a shared reference to the &lt;strong&gt;entire&lt;&#x2F;strong&gt; request in another extractor.&lt;br &#x2F;&gt;
This is not a major problem for other frameworks (e.g. &lt;code&gt;actix-web&lt;&#x2F;code&gt;)—they often force you to clone data in your extractors, therefore the request is only borrowed for the duration of the extraction operation.&lt;br &#x2F;&gt;
That&#x27;s not the case for Pavex—extractors (and constructors in general) can borrow data from the incoming request which can then be used in your request handler.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, consider the following usage of the &lt;code&gt;QueryParams&lt;&#x2F;code&gt; extractor:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Debug, Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;QueryParams&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: Cow&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;params&lt;&#x2F;span&gt;&lt;span&gt;: QueryParams&amp;lt;&amp;#39;_&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;: BufferedBody) -&amp;gt; Response {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;QueryParams&lt;&#x2F;code&gt; lets you avoid allocations when extracting the &lt;code&gt;name&lt;&#x2F;code&gt; query parameter&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#cow&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, borrowing data directly from the request.&lt;br &#x2F;&gt;
&lt;code&gt;BufferedBody&lt;&#x2F;code&gt;, instead, takes ownership of the streaming request body and buffers it in memory.&lt;&#x2F;p&gt;
&lt;p&gt;If &lt;code&gt;QueryParams&lt;&#x2F;code&gt; were to take a &lt;code&gt;&amp;amp;Request&lt;&#x2F;code&gt; as input, this wouldn&#x27;t compile: you can&#x27;t consume the request body in &lt;code&gt;BufferedBody&lt;&#x2F;code&gt; while you&#x27;re holding a shared reference to the entire request in &lt;code&gt;QueryParams&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I could &quot;solve&quot; this problem by writing extensive documentation and explaining to people that they&#x27;re holding it wrong. Or I could just remove the problem altogether: no &lt;code&gt;Request&lt;&#x2F;code&gt;, no problem!
If you&#x27;re writing an extractor that needs access to both the head and the body, you can just ask Pavex to give you both as inputs (as we just did in the &lt;code&gt;handler&lt;&#x2F;code&gt; function above).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;our-own-response-type&quot;&gt;Our own &lt;code&gt;Response&lt;&#x2F;code&gt; type&lt;&#x2F;h3&gt;
&lt;p&gt;The second change is that Pavex no longer exposes the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;http&#x2F;0.2.9&#x2F;http&#x2F;response&#x2F;struct.Response.html&quot;&gt;&lt;code&gt;Response&lt;&#x2F;code&gt; type from the &lt;code&gt;http&lt;&#x2F;code&gt; crate&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
The choice was driven by two factors:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Extensions&lt;&#x2F;li&gt;
&lt;li&gt;API control&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;extensions-considered-harmful&quot;&gt;Extensions considered harmful&lt;&#x2F;h4&gt;
&lt;p&gt;The &lt;code&gt;Response&lt;&#x2F;code&gt; type from the &lt;code&gt;http&lt;&#x2F;code&gt; crate includes &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;http&#x2F;0.2.9&#x2F;http&#x2F;response&#x2F;struct.Response.html#method.extensions&quot;&gt;an &lt;code&gt;Extensions&lt;&#x2F;code&gt; field&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
Most Rust web frameworks follow the same design, even if they don&#x27;t necessarily rely on the &lt;code&gt;http&lt;&#x2F;code&gt; crate: &lt;code&gt;actix-web&lt;&#x2F;code&gt; has &lt;code&gt;HttpResponse::extensions&lt;&#x2F;code&gt;, &lt;code&gt;tide&lt;&#x2F;code&gt; has &lt;code&gt;Response::insert_ext&lt;&#x2F;code&gt;, etc.&lt;&#x2F;p&gt;
&lt;p&gt;You can think of extensions as a side-channel: it lets you pass arbitrary data from your request handler to your middlewares (or between middlewares). It is backed by a typemap—i.e. a &lt;code&gt;HashMap&lt;&#x2F;code&gt; where the key is a type id and the value is a &lt;code&gt;Box&amp;lt;dyn Any&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Extensions are powerful, but they are also a major source of pain and bugs.&lt;br &#x2F;&gt;
When you try to retrieve data, you need to downcast it to the right type. The compiler can&#x27;t help you if you try to retrieve a value with the wrong type or that doesn&#x27;t exist at all: you&#x27;ll find out at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s spooky action at a distance: different parts of your codebase (and its dependencies!) must be kept in sync to ensure everything works smoothly, but the relationship is entirely implicit.&lt;br &#x2F;&gt;
You can&#x27;t tell from the signature of a function whether (or what!) it will read or write to the extensions. You can&#x27;t know if a certain middleware requires another middleware to be present in the chain in order to insert a certain value in the response extensions. You also can&#x27;t know if a middleware will end up overwriting an extension value that you&#x27;re using or inserting in your request handler.&lt;&#x2F;p&gt;
&lt;p&gt;Pavex works differently: no first-class side-channels.&lt;br &#x2F;&gt;
All the data that you want to use or return in a constructor or in a middleware must be explicitly declared in their signatures. This allows Pavex to reason about the data flow in our application at compile-time, flagging issues early and providing helpful errors.&lt;br &#x2F;&gt;
We have no use for extensions on our &lt;code&gt;Response&lt;&#x2F;code&gt; type.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;api-control&quot;&gt;API control&lt;&#x2F;h4&gt;
&lt;p&gt;Relying entirely on &lt;code&gt;http::Response&lt;&#x2F;code&gt; is also a major limitation when it comes to API control.&lt;br &#x2F;&gt;
We can&#x27;t add new methods to it, we can&#x27;t change the signature of existing methods, etc.&lt;br &#x2F;&gt;
For a framework, this is a major limitation.&lt;&#x2F;p&gt;
&lt;p&gt;By introducing our own &lt;code&gt;Response&lt;&#x2F;code&gt; type, we can control the API surface and evolve it over time.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;preserving-interoperability&quot;&gt;Preserving interoperability&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;code&gt;http&lt;&#x2F;code&gt; is a foundational crate. Its types are used by many other crates in the ecosystem as the &quot;default&quot; HTTP representations.&lt;br &#x2F;&gt;
Moving away from it introduces an interoperability problem.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily enough, I agree with most of the design choices made by &lt;code&gt;http&lt;&#x2F;code&gt;&#x27;s authors when it comes to HTTP responses. Pavex&#x27;s &lt;code&gt;Response&lt;&#x2F;code&gt; is just a newtype wrapper around &lt;code&gt;http::Response&lt;&#x2F;code&gt;, to enforce what we discussed above.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;Response&amp;lt;Body = BoxBody&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;inner&lt;&#x2F;span&gt;&lt;span&gt;: http::Response&amp;lt;Body&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can convert (for free) a &lt;code&gt;pavex::Response&lt;&#x2F;code&gt; to an &lt;code&gt;http::Response&lt;&#x2F;code&gt; and vice-versa. Interoperability is preserved.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;typedbody-and-intoresponse&quot;&gt;&lt;code&gt;TypedBody&lt;&#x2F;code&gt; and &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Drunk with the newly found power of having our own &lt;code&gt;Response&lt;&#x2F;code&gt; type, I started to experiment with the machinery to build responses.&lt;&#x2F;p&gt;
&lt;p&gt;Request handlers in Pavex must return a type that implements the &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; trait&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#error-response&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. This design is very similar (and inspired by) &lt;code&gt;actix-web&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;Responder&lt;&#x2F;code&gt; trait and &lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; trait.&lt;br &#x2F;&gt;
Both &lt;code&gt;actix-web&lt;&#x2F;code&gt; and &lt;code&gt;axum&lt;&#x2F;code&gt; let you return a response body (e.g. a &lt;code&gt;Json&lt;&#x2F;code&gt; type) and automatically convert it into a &lt;code&gt;Response&lt;&#x2F;code&gt; for you, using the correct &lt;code&gt;Content-Type&lt;&#x2F;code&gt; header.&lt;br &#x2F;&gt;
They go one step further though: they &lt;strong&gt;make an assumption about the status code of the response&lt;&#x2F;strong&gt;. A &lt;code&gt;200 OK&lt;&#x2F;code&gt; is a reasonable default, but it is not always the right choice. What about a newly created resource, with its &lt;code&gt;201 Created&lt;&#x2F;code&gt;? Or a &lt;code&gt;204 No Content&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;The more I looked at it, the more I feel that we are conflating two different concerns:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the handling of typed response bodies (and their &lt;code&gt;Content-Header&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;the value of the corresponding response head (i.e. status code and headers).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I decided to split them apart.&lt;&#x2F;p&gt;
&lt;p&gt;Pavex will be very conservative with &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;. It will only be implemented for a few types, the ones that don&#x27;t require us to make assumptions about the status code—e.g. &lt;code&gt;ResponseHead&lt;&#x2F;code&gt;, &lt;code&gt;StatusCode&lt;&#x2F;code&gt; itself.&lt;br &#x2F;&gt;
A new trait, &lt;code&gt;TypedBody&lt;&#x2F;code&gt;, will instead encapsulate the machinery for converting a (typed) response body into its representation on the wire:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;TypedBody {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Body: RawBody&amp;lt;Data = Bytes&amp;gt; + Send + Sync + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; The header value that should be used as `Content-Type` when
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; returning this `Response`
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;content_type&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; HeaderValue;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; The actual body type travelling on the wire.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; It must implement the `RawBody` trait.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Body;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can implement &lt;code&gt;TypedBody&lt;&#x2F;code&gt; for your own types, or use the provided implementations for &lt;code&gt;String&lt;&#x2F;code&gt;, &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;&#x2F;code&gt;, &lt;code&gt;Bytes&lt;&#x2F;code&gt;, &lt;code&gt;&amp;amp;&#x27;static str&lt;&#x2F;code&gt;, &lt;code&gt;Json&lt;&#x2F;code&gt;, &lt;code&gt;Html&lt;&#x2F;code&gt;, etc.&lt;&#x2F;p&gt;
&lt;p&gt;The implementation is quite straightforward—let&#x27;s look at the one for &lt;code&gt;Html&lt;&#x2F;code&gt; as an example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::http::HeaderValue;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex::response::body::raw::{Full, Bytes};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;mime::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;TEXT_HTML_UTF_8&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; A `Response` body with `Content-Type` set to
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; `text&#x2F;html; charset=utf-8`.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;Html(Bytes);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;TypedBody &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;Html {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Body = Full&amp;lt;Bytes&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;content_type&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; HeaderValue {
&lt;&#x2F;span&gt;&lt;span&gt;        HeaderValue::from_static(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;TEXT_HTML_UTF_8&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Body {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The response is fully buffered in memory, therefore
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; we wrap the corresponding bytes in a `Full` body.
&lt;&#x2F;span&gt;&lt;span&gt;        Full::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Typed bodies can then be passed to the corresponding method on the &lt;code&gt;Response&lt;&#x2F;code&gt; type:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; html: Html = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;h1&amp;gt;Hello world!&amp;lt;&#x2F;h1&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;Response::ok().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;typed_body&lt;&#x2F;span&gt;&lt;span&gt;(html)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since &lt;code&gt;Response&lt;&#x2F;code&gt; provides a shorthand constructor for all status codes the resulting code remains quite compact.&lt;br &#x2F;&gt;
As an added bonus, this drives further standardisation in the signatures of request handlers: they will almost always be returning a &lt;code&gt;Response&lt;&#x2F;code&gt;. One less thing to worry about when getting started.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;one-pavex-crate-to-rule-them-all&quot;&gt;One &lt;code&gt;pavex&lt;&#x2F;code&gt; crate to rule them all&lt;&#x2F;h2&gt;
&lt;p&gt;Up until now, a user of Pavex had to interact with two different crates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pavex_builder&lt;&#x2F;code&gt;, which contained the &lt;code&gt;Blueprint&lt;&#x2F;code&gt; type and all the machinery required to define the specification (routes, request handlers, constructors) for your API.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;pavex_runtime&lt;&#x2F;code&gt;, which contained the types and traits needed to write request handlers and middlewares (request, responses, headers, extractors, etc).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Having two separate crates brings some benefits, mostly around compile times:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pay for what you use&lt;&#x2F;strong&gt;. If you only need one of them, you don&#x27;t need to compile the other one.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Parallel builds&lt;&#x2F;strong&gt;. When you need them both, &lt;code&gt;cargo&lt;&#x2F;code&gt; can still compile them in parallel since they don&#x27;t depend on each other (check out &lt;code&gt;cargo build --timings&lt;&#x2F;code&gt; for your own projects, if you haven&#x27;t done it yet!)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;It is a trade-off though.&lt;br &#x2F;&gt;
The surface-level complexity exposed to the user is higher—they need to understand how the two crates interact with each other. If the crates are versioned independently, users also need to figure out which versions are compatible.&lt;&#x2F;p&gt;
&lt;p&gt;When looking at Pavex, the benefits didn&#x27;t actually materialise in practice.&lt;br &#x2F;&gt;
An application built with Pavex has to define its request handlers (therefore depend on &lt;code&gt;pavex_runtime&lt;&#x2F;code&gt;, either directly or transitively) and then register them against a &lt;code&gt;Blueprint&lt;&#x2F;code&gt; (therefore depend on &lt;code&gt;pavex_builder&lt;&#x2F;code&gt;). You always pay for everything.&lt;&#x2F;p&gt;
&lt;p&gt;The dependency graph is about to change as well: &lt;code&gt;pavex_runtime&lt;&#x2F;code&gt; will soon take a dependency on &lt;code&gt;pavex_builder&lt;&#x2F;code&gt;. I plan to introduce presets, along the same lines of the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;laravel.com&#x2F;docs&#x2F;10.x&#x2F;middleware#middleware-groups&quot;&gt;&lt;code&gt;api&lt;&#x2F;code&gt; and &lt;code&gt;browser&lt;&#x2F;code&gt; middleware groups in Laravel&lt;&#x2F;a&gt;; a set of &quot;core&quot; constructors and middlewares that you often want to use in your application to boost your productivity.&lt;br &#x2F;&gt;
When presets ship, we can say goodbye to build parallelism as well.&lt;&#x2F;p&gt;
&lt;p&gt;Given the above, I bit the bullet and merged the two crates into a single &lt;code&gt;pavex&lt;&#x2F;code&gt; crate.&lt;&#x2F;p&gt;
&lt;p&gt;I haven&#x27;t given up on compile-time optimisation though!&lt;br &#x2F;&gt;
I plan to recover the &quot;pay for what you use&quot; aspect by using feature flags to control which parts of the crate are compiled. It won&#x27;t be useful to applications, but it might make a difference to custom tooling built on top of Pavex (e.g. a GUI to inspect your &lt;code&gt;Blueprint&lt;&#x2F;code&gt; doesn&#x27;t need the runtime types).&lt;br &#x2F;&gt;
I am also exploring the viability of recovering (some) parallel compilation by having a &quot;facade&quot; crate (&lt;code&gt;pavex&lt;&#x2F;code&gt;) which re-exports from multiple (largely independent) sub-crates. It is somewhat involved, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;algo_luca&#x2F;status&#x2F;1669994949390094338&quot;&gt;especially when it comes to documentation&lt;&#x2F;a&gt;, but it want to explore it further in the future.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-realworld-specification&quot;&gt;The Realworld specification&lt;&#x2F;h2&gt;
&lt;p&gt;Nothing gives you a better feeling for a framework than trying to build something with it.&lt;&#x2F;p&gt;
&lt;p&gt;As anticipated in the previous report, I started to implement the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;codebase.show&#x2F;projects&#x2F;realworld&quot;&gt;Realworld specification&lt;&#x2F;a&gt; using Pavex.&lt;br &#x2F;&gt;
The Realworld specification is a set of requirements for a blogging platform (named Conduit), with a reference implementation in many different languages and frameworks. It&#x27;s a great way to get a feeling for a framework and its ergonomics.&lt;&#x2F;p&gt;
&lt;p&gt;The Pavex implementation is not complete yet, but you can already check out the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;main&#x2F;examples&#x2F;realworld&quot;&gt;source code&lt;&#x2F;a&gt; if you are curious.&lt;&#x2F;p&gt;
&lt;p&gt;Most of the runtime decisions that I discussed above were driven by my own observations while trying to implement the spec.&lt;br &#x2F;&gt;
As it happens, I also identified a variety of bugs and missing features as I was going along. I won&#x27;t go into details (this update is long enough already!) but you can have a look at the PRs if you are curious:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;🎉 &lt;code&gt;QueryParams&lt;&#x2F;code&gt; extractor (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;65&quot;&gt;#65&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🎉 &lt;code&gt;Json&lt;&#x2F;code&gt; and &lt;code&gt;BufferedBody&lt;&#x2F;code&gt; extractors, with default body size limits (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;66&quot;&gt;#66&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;67&quot;&gt;#67&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🎉 Get syntax highlighting and go-to-definition working inside the &lt;code&gt;f!&lt;&#x2F;code&gt; macro (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;9518d660013b5c03529337de24c2d9958bc40ce4&quot;&gt;commit&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠️ Fix validation for route paths to ensure expressivity while enforcing a canonical representation (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;70&quot;&gt;#70&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠 Handle blueprints with multiple levels of nesting (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;68&quot;&gt;#68&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠️ [Reflection] Handle type aliases when working with methods (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;79&quot;&gt;#79&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠️ [Reflection] Handle standalone re-exported items (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;79&quot;&gt;#79&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠️ [Reflection] Tweak deserialization limits for the Rustdoc JSON docs of pathological crates like &lt;code&gt;typenum&lt;&#x2F;code&gt; (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;79&quot;&gt;#79&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠 [Reflection] Handle glob re-exports from local modules (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;pull&#x2F;73&quot;&gt;#73&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;🛠 [Reflection] Handle generic arguments defined in a dependency (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;a950e545992e9d4927ad45186ec037179bf9117d&quot;&gt;commit&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Yes, building a compile-time reflection engine on top of Rustdoc JSON docs is a nasty business.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-next&quot;&gt;What&#x27;s next&lt;&#x2F;h1&gt;
&lt;p&gt;I plan to continue (and complete?) the Realworld implementation in July.&lt;br &#x2F;&gt;
There&#x27;s work to be done with respect to our error handling story and I &lt;strong&gt;really&lt;&#x2F;strong&gt; need to add support for middlewares—debugging without a logging middleware is a pain!&lt;&#x2F;p&gt;
&lt;p&gt;Once the above are in place, I can start working on docs: refining the API reference, writing tutorials and conceptual guides. It&#x27;s going to be a lot of work, but it&#x27;s absolutely vital.&lt;&#x2F;p&gt;
&lt;p&gt;July is also going to be a busy month for my personal life.&lt;br &#x2F;&gt;
I have to work my way through a &lt;strong&gt;ton&lt;&#x2F;strong&gt; of paperwork to finalise my relocation to Italy. Not fun at all, believe me.&lt;br &#x2F;&gt;
I&#x27;ll also be starting a new job as a Principal Engineering Consultant at Mainmatter. I&#x27;ll be partnerning with teams across the world who are looking to adopt Rust or scale its usage (check out &lt;a href=&quot;&#x2F;posts&#x2F;mainmatter&#x2F;&quot;&gt;this post&lt;&#x2F;a&gt; if you want know to more about it).&lt;&#x2F;p&gt;
&lt;p&gt;See you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;14m60i9&#x2F;pavex_devlog_5_redesigning_our_runtime_types_rust&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of Pavex on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;cow&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;You must use &lt;code&gt;Cow&amp;lt;&#x27;a, str&amp;gt;&lt;&#x2F;code&gt; rather than &lt;code&gt;&amp;amp;str&lt;&#x2F;code&gt; because allocations sometimes &lt;strong&gt;cannot&lt;&#x2F;strong&gt; be avoided. In the case of query parameters, you are forced to allocate a fresh &lt;code&gt;String&lt;&#x2F;code&gt; if the raw query parameter contains any URL-encoded symbols.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;error-response&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Or a &lt;code&gt;Result&lt;&#x2F;code&gt; whose &lt;code&gt;Ok&lt;&#x2F;code&gt; variant implements the &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; trait. We don&#x27;t expect errors to implement &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;: you need to register a dedicated error handler to convert them to a response, with the advantage of being able to customise the rendered response for errors defined in other crates (e.g. Pavex&#x27;s extractor errors) and take advantage of dependency injection when building the response.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-06-29T08:08:10.47Z&quot;&gt;June 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-04&amp;#x2F;&quot;&gt;Pavex Report #4&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-06&amp;#x2F;&quot;&gt;Pavex Report #6&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>I&#x27;m joining Mainmatter</title>
          <pubDate>Mon, 19 Jun 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/mainmatter/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/mainmatter/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/mainmatter/">&lt;h1 id=&quot;tl-dr-my-next-step&quot;&gt;TL;DR: My next step&lt;&#x2F;h1&gt;
&lt;p&gt;In July I&#x27;ll join &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mainmatter.com&#x2F;&quot;&gt;Mainmatter&lt;&#x2F;a&gt; as a Principal Engineering Consultant.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;who-is-mainmatter&quot;&gt;Who is Mainmatter?&lt;&#x2F;h1&gt;
&lt;p&gt;Mainmatter is a consulting company headquartered in Germany, with a team distributed across most of Europe.&lt;br &#x2F;&gt;
We got in touch for the first time last year, in 2022: they were organising the first edition of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eurorust.eu&#x2F;&quot;&gt;EuroRust&lt;&#x2F;a&gt; in Berlin and I had the pleasure of helping out by reviewing talk proposals.&lt;br &#x2F;&gt;
EuroRust was not a one-off. The conference is part of Mainmatter&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mainmatter.com&#x2F;blog&#x2F;2022&#x2F;10&#x2F;12&#x2F;making-a-strategic-bet-on-rust&#x2F;&quot;&gt;strategic bet on Rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s where I come in!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Speaking of which, the second edition of EuroRust is around the corner!&lt;br &#x2F;&gt;
It&#x27;ll be in Brussels (Belgium), 12-13 October 2023. You&#x27;re still in time to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eurorust.eu&quot;&gt;grab a ticket&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;my-rust-thesis&quot;&gt;My Rust thesis&lt;&#x2F;h1&gt;
&lt;p&gt;My first encounter with Rust was in 2018, almost by chance.&lt;br &#x2F;&gt;
I was an ML engineer at the time, eating Python for breakfast, lunch and dinner. I had heard of this &quot;Rust thing&quot; from my CTO, a spill-over from some discussion on HackerNews.&lt;br &#x2F;&gt;
It caught my attention. I was struggling to write performant prototypes of new algorithms and I wondered: is it a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=odI_LY8AIqo&quot;&gt;viable alternative to C&#x2F;C++ for writing native Python extensions&lt;&#x2F;a&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;Rust has been a constant in my professional life ever since.&lt;br &#x2F;&gt;
I&#x27;ve seen the discourse around it evolve, from &quot;up-and-coming&quot; to &quot;the next big thing&quot; to &quot;the next big thing that&#x27;s already here&quot;.&lt;br &#x2F;&gt;
I&#x27;ve seen companies of all sizes adopt it, first for small projects and then for mission-critical systems.&lt;br &#x2F;&gt;
I&#x27;ve seen the most disparate industries embrace it, from gaming to fintech to automotive.&lt;&#x2F;p&gt;
&lt;p&gt;Rust truly embodies the idea of a &lt;em&gt;general purpose&lt;&#x2F;em&gt; programming language.&lt;br &#x2F;&gt;
It spans the &lt;strong&gt;entire stack&lt;&#x2F;strong&gt;, from Linux drivers to web development. It isn&#x27;t the &lt;em&gt;perfect&lt;&#x2F;em&gt; language for most domains, but it&#x27;s a &lt;em&gt;damn good&lt;&#x2F;em&gt; language for most domains. This flexibility is what makes Rust so compelling: you need to invest time and effort to master it, but you&#x27;re rewarded with a tool that can support you successfully in most of your endeavours.&lt;br &#x2F;&gt;
The return on investment is huge.&lt;&#x2F;p&gt;
&lt;p&gt;Over the next decade, I expect Rust to become &lt;strong&gt;ubiquitous&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Every developer will be working with it, either directly (by writing Rust code) or indirectly (by using libraries in other programming languages that wrap a Rust core).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-web-is-the-next-frontier&quot;&gt;The web is the next frontier&lt;&#x2F;h1&gt;
&lt;p&gt;That&#x27;s all cool and interesting, but you might wonder: what does it have to do with me? Or Mainmatter?&lt;&#x2F;p&gt;
&lt;p&gt;My focus right now is on the first group of users, those writing Rust code directly.&lt;br &#x2F;&gt;
What can we do to make their lives easier? What is the best way to support them?&lt;&#x2F;p&gt;
&lt;p&gt;I identified API development as the next &quot;big&quot; frontier for Rust adoption a few years ago.&lt;br &#x2F;&gt;
I&#x27;ve pitched, delivered and reaped the benefits of Rust on the backend when I was working at TrueLayer.&lt;br &#x2F;&gt;
I&#x27;ve seen it again at AWS.&lt;&#x2F;p&gt;
&lt;p&gt;I want everybody else to experience the same.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&#x2F;&quot;&gt;&quot;Zero to Production in Rust&quot;&lt;&#x2F;a&gt; gave me the chance to speak with so many different people embarking on this journey.&lt;br &#x2F;&gt;
It isn&#x27;t a phenomenon limited to a few early adopters or Big Tech anymore. More and more companies are picking up Rust for their backend services.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-mainmatter&quot;&gt;Why Mainmatter?&lt;&#x2F;h1&gt;
&lt;p&gt;I want to be close to this wave of adopters. I want to see it succeed.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve experimented with going &lt;em&gt;deep&lt;&#x2F;em&gt;: focus on one company, nail a &quot;productive&quot; Rust stack and then scale it across the organisation. Later export the learnings to the community.&lt;&#x2F;p&gt;
&lt;p&gt;It has its limitations: each company has its own unique set of problems and constraints, which aren&#x27;t always relevant to the broader ecosystem. They might not be &lt;em&gt;representative&lt;&#x2F;em&gt;: what works for AWS often doesn&#x27;t work (or doesn&#x27;t even register as an issue!) for a small startup. Incentives are also not always aligned.&lt;&#x2F;p&gt;
&lt;p&gt;Which bring me back to consulting: I want an opportunity to partner with a larger portfolio of companies. I want to experiment with going &lt;em&gt;wide&lt;&#x2F;em&gt;.&lt;br &#x2F;&gt;
I see this opportunity in Mainmatter, and we decided to partner up!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;but-what-will-you-actually-be-doing&quot;&gt;But what will you actually be doing?&lt;&#x2F;h1&gt;
&lt;p&gt;I&#x27;ll be working together with clients, both hands-in-the-editor and drawing boxes and arrows on the board. Whatever it takes to solve the problem at hand.&lt;&#x2F;p&gt;
&lt;p&gt;You&#x27;ll also find me running workshops, trainings and speaking at conferences—perhaps even more so than before.&lt;br &#x2F;&gt;
One workshop is already on the calendar: a deep-dive into telemetry for Rust projects, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eurorust.eu&#x2F;workshops&#x2F;you-cant-fix-what-you-cant-see-telemetry-for-rust-apis&#x2F;&quot;&gt;&quot;You can&#x27;t fix what you can&#x27;t see&quot;&lt;&#x2F;a&gt;. There are still a few tickets left, so &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eurorust.eu&quot;&gt;grab one&lt;&#x2F;a&gt; if you&#x27;re interested.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re interested in working together, don&#x27;t hesitate to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mainmatter.com&#x2F;contact&#x2F;&quot;&gt;get in touch&lt;&#x2F;a&gt;!&lt;br &#x2F;&gt;
I&#x27;d love to hear from you, whether you&#x27;re evaluating Rust, starting your journey, or looking for ways to take it to the next level.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s incredible work to be done in this space, and I&#x27;m excited to play a part.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Pavex, progress report #4: performance is a feature</title>
          <pubDate>Wed, 24 May 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-04/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-04/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-04/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is progress report about &lt;code&gt;pavex&lt;&#x2F;code&gt;, a new Rust web framework that I have been working on. It is currently
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;early stages of development&lt;&#x2F;a&gt;, working towards its first alpha release.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; to learn more about the vision!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;It&#x27;s time for another progress report on Pavex, covering the work done in May!&lt;&#x2F;p&gt;
&lt;p&gt;At a glance:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Pavex has a logo, at last!&lt;&#x2F;li&gt;
&lt;li&gt;I built &lt;a href=&quot;&#x2F;posts&#x2F;cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a new tool that simplifies code generation in Pavex projects. It enables automatic regeneration of the server SDK crate, eliminating the need for manual updates.&lt;&#x2F;li&gt;
&lt;li&gt;Multiple rounds of performance optimizations to make sure that code generation doesn&#x27;t become a bottleneck in your development workflow.&lt;br &#x2F;&gt;
The execution time of &lt;code&gt;pavex_cli&lt;&#x2F;code&gt; went from ~3.7s to ~0.9s on our Realworld example, a &lt;strong&gt;4x speedup&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let&#x27;s dive in!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;13qlzrg&#x2F;pavex_a_new_rust_web_framework_report_4&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#logo&quot;&gt;Logo&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#streamlining-code-generation-cargo-px&quot;&gt;Streamlining code-generation: &lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#crate-generation-file-generation&quot;&gt;Crate generation != file generation&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#using-cargo-px-with-pavex&quot;&gt;Using &lt;code&gt;cargo-px&lt;&#x2F;code&gt; with Pavex&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#streamlining-code-generation-performance&quot;&gt;Streamlining code-generation: performance&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#the-methodology&quot;&gt;The methodology&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#the-baseline&quot;&gt;The baseline&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#optimisation-1-caching-3rd-party-crates&quot;&gt;Optimisation #1: caching 3rd party crates&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#optimisation-2-lazy-deserialization&quot;&gt;Optimisation #2: lazy deserialization&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#optimisation-3-don-t-touch-the-disk-if-you-don-t-have-to&quot;&gt;Optimisation #3: don&#x27;t touch the disk if you don&#x27;t have to&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#optimisation-4-memoise-the-list-of-crates-we-need&quot;&gt;Optimisation #4: memoise the list of crates we need&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#optimisation-n-more-to-come&quot;&gt;Optimisation #N: more to come&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;logo&quot;&gt;Logo&lt;&#x2F;h2&gt;
&lt;p&gt;Nothing says &quot;things are getting serious&quot; like a logo. And we finally have one for Pavex!&lt;&#x2F;p&gt;
&lt;style scoped&gt;
&#x2F;* 0 to 299 *&#x2F;
.pavex_logo {
     max-width: 100%;
}
&#x2F;* 300 to X *&#x2F;
@media (min-width: 500px) {
    .pavex_logo {   
        max-width: 50%;
    }
}
&lt;&#x2F;style&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;pavex_logo.webp&quot; alt=&quot;Pavex&#x27;s new logo&quot; class=&quot;pavex_logo&quot;&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;I was keen on giving the project a visual identity, so I finally bit the bullet and hired a designer for the job. We went through a few different iterations, but I&#x27;m quite satisfied with the end result.&lt;br &#x2F;&gt;
A heartfelt thanks to all the people who provided feedback on the early sketches.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;streamlining-code-generation-cargo-px&quot;&gt;Streamlining code-generation: &lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Code generation is a key step in a Pavex project:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;You define the &lt;code&gt;Blueprint&lt;&#x2F;code&gt; for your application (routes, constructors, error handlers)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(pavex_auth::session_token), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::home_page));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Pavex&#x27;s compiler processes the &lt;code&gt;Blueprint&lt;&#x2F;code&gt; (compile-time dependency injection, yay!) and &lt;strong&gt;generates&lt;&#x2F;strong&gt; the runtime scaffolding for your application, the &lt;strong&gt;server SDK&lt;&#x2F;strong&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#smithy&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The server SDK is not a stand-alone Rust file. It has dependencies—it must import code from the crates that define the request handlers, constructors and error handlers that you specified in your &lt;code&gt;Blueprint&lt;&#x2F;code&gt;, as well as other crates in the Pavex ecosystem.
It is a full-blown Rust library, that requires its own &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; file.&lt;&#x2F;p&gt;
&lt;p&gt;You want Pavex to take care of it, automatically, because having to manually update its &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; every time you change your &lt;code&gt;Blueprint&lt;&#x2F;code&gt; would be an awful experience.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;crate-generation-file-generation&quot;&gt;Crate generation != file generation&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;strong&gt;Rust ecosystem doesn&#x27;t have a good story for crate generation&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Build scripts rely on the assumption that you aren&#x27;t going to modify the manifest of the crate you are building. They also don&#x27;t allow you to invoke &lt;code&gt;cargo&lt;&#x2F;code&gt; commands&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#metadata-exception&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; from within the build script itself, which is a requirement for Pavex&#x27;s compile-time reflection, since it relies on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rust-lang.github.io&#x2F;rfcs&#x2F;2963-rustdoc-json.html&quot;&gt;&lt;code&gt;rustdoc&lt;&#x2F;code&gt;&#x27;s JSON output&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;But build scripts are convenient!&lt;br &#x2F;&gt;
&lt;code&gt;cargo&lt;&#x2F;code&gt; takes care of running them every single time it makes sense to do so. You don&#x27;t have to &lt;em&gt;think&lt;&#x2F;em&gt; about them.&lt;br &#x2F;&gt;
Having a stand-alone script that you need to invoke manually every time you change your &lt;code&gt;Blueprint&lt;&#x2F;code&gt; makes for a much worse experience. I tried it out for a couple of days, for the sake of science, while working on Pavex&#x27;s examples: it just doesn&#x27;t work. You forget to regenerate the server SDK, and you end up with a broken build.&lt;&#x2F;p&gt;
&lt;p&gt;Having exhausted all available options, &lt;strong&gt;I built a new tool&lt;&#x2F;strong&gt;: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; (&lt;code&gt;cargo&lt;&#x2F;code&gt; &lt;strong&gt;P&lt;&#x2F;strong&gt;ower e&lt;strong&gt;X&lt;&#x2F;strong&gt;tensions)&lt;br &#x2F;&gt;
It lets you specify a crate generator in the manifest of the soon-to-be-generated crate.
It then transparently invokes the code generator when necessary, assuming you route your commands through &lt;code&gt;cargo-px&lt;&#x2F;code&gt; instead of &lt;code&gt;cargo&lt;&#x2F;code&gt; (i.e. invoke &lt;code&gt;cargo px build&lt;&#x2F;code&gt; rather &lt;code&gt;cargo build&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt; is not tied to Pavex: it is a general-purpose tool that can be leveraged by any Rust project that needs to generate an entire crate rather than a stand-alone self-contained source file, a requirement &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cornucopia-rs&#x2F;cornucopia&#x2F;issues&#x2F;186&quot;&gt;that is more common than you might think&lt;&#x2F;a&gt;. Check out the &lt;a href=&quot;&#x2F;posts&#x2F;cargo-px&#x2F;&quot;&gt;announcement blog post&lt;&#x2F;a&gt; for a deep-dive on the problem space and the precise mechanics of &lt;code&gt;cargo-px&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;using-cargo-px-with-pavex&quot;&gt;Using &lt;code&gt;cargo-px&lt;&#x2F;code&gt; with Pavex&lt;&#x2F;h3&gt;
&lt;p&gt;When it comes to Pavex, the integration with &lt;code&gt;cargo-px&lt;&#x2F;code&gt; works as follows:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;You write a small binary that will act as the &lt;strong&gt;code generator&lt;&#x2F;strong&gt; (i.e. the build script) for the server SDK, starting from the &lt;code&gt;Blueprint&lt;&#x2F;code&gt;:&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;cargo_px_env::generated_pkg_manifest_path;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_cli_client::Client;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;api_core::api_blueprint;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::error::Error;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn Error&amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; generated_dir = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;generated_pkg_manifest_path&lt;&#x2F;span&gt;&lt;span&gt;()?.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;parent&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    Client::new()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;generate&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;api_blueprint&lt;&#x2F;span&gt;&lt;span&gt;(), generated_dir.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;()?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;You create the &lt;strong&gt;server SDK&lt;&#x2F;strong&gt; as an empty library crate via &lt;code&gt;cargo new --lib api_server_sdk&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;You add an entry into the &lt;code&gt;[metadata]&lt;&#x2F;code&gt; section of the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; of server SDK, pointing at the code generator:&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span&gt;[package.metadata.px.generate]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;generator_type &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cargo_workspace_binary&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# The name of the binary we defined above
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;generator_name &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;bp&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That&#x27;s it!&lt;br &#x2F;&gt;
Every time you invoke &lt;code&gt;cargo px build&lt;&#x2F;code&gt; or &lt;code&gt;cargo px check&lt;&#x2F;code&gt;, &lt;code&gt;cargo-px&lt;&#x2F;code&gt; will invoke the code generator, which will in turn regenerate the server SDK crate if needed.&lt;br &#x2F;&gt;
I have plans to streamline this further with a project scaffolding tool for Pavex, which will take care of setting up the server SDK generator for you.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been using &lt;code&gt;cargo-px&lt;&#x2F;code&gt; for a few weeks now when iterating on Pavex&#x27;s examples, and it has been working great. It just takes a bit of conscious effort to remember to type &lt;code&gt;cargo px [...]&lt;&#x2F;code&gt; instead of &lt;code&gt;cargo [...]&lt;&#x2F;code&gt;; I&#x27;ll be probably working on a shim soon enough to make it transparent (as an opt-in feature).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;streamlining-code-generation-performance&quot;&gt;Streamlining code-generation: performance&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Make it work, make it right, make it fast.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;With &lt;code&gt;cargo-px&lt;&#x2F;code&gt;, we covered the first two points.&lt;br &#x2F;&gt;
Let&#x27;s talk about &lt;strong&gt;performance&lt;&#x2F;strong&gt; now!&lt;&#x2F;p&gt;
&lt;p&gt;Code generation is Pavex&#x27;s super power, but it comes at a cost: it adds one more step to your build process.&lt;br &#x2F;&gt;
I firmly believe that a fast feedback loop is essential. It can make or break you productivity, and I don&#x27;t want Pavex to get in the way.&lt;&#x2F;p&gt;
&lt;p&gt;If the code generator has to be invoked every time you build your project, it needs to be &lt;strong&gt;fast&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
The goal is to stay under 1 second, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hpbn.co&#x2F;primer-on-web-performance&#x2F;#speed-performance-and-human-perception&quot;&gt;the difference between &quot;the machine is working&quot; and &quot;I&#x27;m going to check Twitter while I wait&quot;&lt;&#x2F;a&gt;. Staying under 300 milliseconds would be even better, but I don&#x27;t think it&#x27;s realistic, as you&#x27;ll see in a moment.&lt;&#x2F;p&gt;
&lt;p&gt;The rest of this section will be a deep-dive into each of the optimisations that I have implemented this month, explaining the rationale behind them and the performance gains that they brought.&lt;br &#x2F;&gt;
If you don&#x27;t care about the finer details, this is the summary:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Optimisation Round&lt;&#x2F;th&gt;&lt;th&gt;Execution time (s)&lt;&#x2F;th&gt;&lt;th&gt;Speedup vs baseline&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Baseline&lt;&#x2F;td&gt;&lt;td&gt;3.677 ± 0.057&lt;&#x2F;td&gt;&lt;td&gt;-&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;#1&lt;&#x2F;td&gt;&lt;td&gt;1.701 ± 0.014&lt;&#x2F;td&gt;&lt;td&gt;2.2x&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;#2&lt;&#x2F;td&gt;&lt;td&gt;1.417 ± 0.013&lt;&#x2F;td&gt;&lt;td&gt;2.6x&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;#3&lt;&#x2F;td&gt;&lt;td&gt;1.196 ± 0.006&lt;&#x2F;td&gt;&lt;td&gt;3.1x&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;#4&lt;&#x2F;td&gt;&lt;td&gt;0.928 ± 0.002&lt;&#x2F;td&gt;&lt;td&gt;4.0x&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;the-methodology&quot;&gt;The methodology&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;44c8f9683c33380e75a609e91a146d7a02c43085&#x2F;examples&#x2F;realworld&quot;&gt;Pavex&#x27;s Realworld example&lt;&#x2F;a&gt; will be our reference project&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#consistency&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. Despite the name, it is just a toy example at the moment: it has a single ping route.&lt;br &#x2F;&gt;
But that&#x27;s fine: it&#x27;ll give us a sense of the performance &lt;strong&gt;floor&lt;&#x2F;strong&gt; of the code generator, the minimum amount of overhead that we expect Pavex to add to our build process.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll be using &lt;code&gt;cargo px check&lt;&#x2F;code&gt; as the command to measure, since it is the command that is going to be invoked most often during a development session: a good proxy measure for the speed of the feedback loop.&lt;&#x2F;p&gt;
&lt;p&gt;We will be measuring the execution time of the &lt;em&gt;second+&lt;&#x2F;em&gt; invocation of &lt;code&gt;cargo px check&lt;&#x2F;code&gt;. The first invocation compiles all the crates in the dependency tree of your project—something that you would have to do anyway even if you had just run &lt;code&gt;cargo check&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;All the measurements have been taken on my personal laptop, a MacBook Air (M2, 16GB RAM, Ventura 13.3.1), using &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sharkdp&#x2F;hyperfine&quot;&gt;&lt;code&gt;hyperfine&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; (&lt;code&gt;hyperfine --warmup 3 &#x27;cargo px check&#x27;&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-baseline&quot;&gt;The baseline&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s establish a baseline, the starting point of our optimisation journey.&lt;br &#x2F;&gt;
&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-04&#x2F;0c211c8e186cc8d409b3b3eb0c908a2d5285fa81&quot;&gt;Before I started to focus on performance&lt;&#x2F;a&gt;, &lt;code&gt;cargo px check&lt;&#x2F;code&gt; took &lt;strong&gt;3.677s ± 0.057s&lt;&#x2F;strong&gt; on the Realworld example.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s kind of a lot!&lt;br &#x2F;&gt;
Where is the time being spent?&lt;&#x2F;p&gt;
&lt;p&gt;We can get an understanding of what goes under the book by asking Pavex to give us telemetry data:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;PAVEX_DEBUG&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;true &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RUST_LOG&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;trace &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span&gt; px check
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pavex will emit &lt;code&gt;tracing&lt;&#x2F;code&gt; logs to stdout as well as saving a Chrome tracing file&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#tracing-chrome&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; that we can inspect using the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ui.perfetto.dev&#x2F;&quot;&gt;Perfetto UI&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is what it looks like:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;a href=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_01.png&quot; target=&quot;_blank&quot; &gt;
        &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_01.png&quot; alt=&quot;Trace data visualised in the Perfetto UI&quot;&gt;
    &lt;&#x2F;a&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;Click on the image to open it in a new tab and zoom in.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;We are spending pretty much all of our time computing the JSON documentation for a variety of crates (the &lt;code&gt;compute_crate_docs&lt;&#x2F;code&gt; spans in the screenshot)—i.e. spawning a &lt;code&gt;cargo rustdoc&lt;&#x2F;code&gt; process for each crate that we need reflection for and waiting for it to complete.&lt;br &#x2F;&gt;
This didn&#x27;t come as a surprise: I had seen from early measurements that &lt;code&gt;rustdoc&lt;&#x2F;code&gt;&#x27;s JSON output is unfortunately not as blazingly fast as I&#x27;d like it to be.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;optimisation-1-caching-3rd-party-crates&quot;&gt;Optimisation #1: caching 3rd party crates&lt;&#x2F;h3&gt;
&lt;p&gt;I went for the most obvious strategy: &lt;strong&gt;if something is expensive, cache it&lt;&#x2F;strong&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#in-memory-cache&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I chose SQLite as our on-disk cache. We have a dedicated table to store the documentation of 3rd party crates, using a composite primary key made of their version, the activated features, their source, the specific toolchain used to generated their docs and a few other bits.&lt;&#x2F;p&gt;
&lt;p&gt;After the first invocation of &lt;code&gt;cargo px check&lt;&#x2F;code&gt;, the cache is warm and we can see a significant improvement in the execution time: &lt;strong&gt;1.701s ±  0.014s&lt;&#x2F;strong&gt;. That&#x27;s a &lt;strong&gt;2.2x speedup&lt;&#x2F;strong&gt;, not too bad!&lt;&#x2F;p&gt;
&lt;p&gt;We can expect the cache to stay warm as well: we only need to compute new docs if you introduce new dependencies, change the features of existing dependencies or change the toolchain used to generate the documentation.&lt;&#x2F;p&gt;
&lt;p&gt;Can we do better? Let&#x27;s look at our trace data again:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;a href=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_02.png&quot; target=&quot;_blank&quot; &gt;
        &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_02.png&quot; alt=&quot;Trace data visualised in the Perfetto UI&quot;&gt;
    &lt;&#x2F;a&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;Click on the image to open it in a new tab and zoom in.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Three spans dominate the execution:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Computing the crate docs for &lt;code&gt;conduit_core&lt;&#x2F;code&gt;, the crate that defines the &lt;code&gt;Blueprint&lt;&#x2F;code&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;Computing the crate docs for &lt;code&gt;pavex_runtime&lt;&#x2F;code&gt;, which comes as a path dependency since we are using a local version of Pavex;&lt;&#x2F;li&gt;
&lt;li&gt;Loading the docs for the toolchain crates (&lt;code&gt;core&lt;&#x2F;code&gt;, &lt;code&gt;alloc&lt;&#x2F;code&gt; and &lt;code&gt;std&lt;&#x2F;code&gt;), which are precomputed and distributed via &lt;code&gt;rustup&lt;&#x2F;code&gt; in the &lt;code&gt;rust-docs-json&lt;&#x2F;code&gt; component.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The first two are tricky to cache: we need to find a way to determine if the a local crate has changed in order to correctly invalidate the cache. Tricky problem, I&#x27;m not keen on tackling it right now.&lt;&#x2F;p&gt;
&lt;p&gt;The last one is interesting: we are not computing the docs for the toolchain crates, we are just loading them from a file and &lt;strong&gt;deserializing&lt;&#x2F;strong&gt; them from JSON.&lt;br &#x2F;&gt;
It turns out that the JSON docs for &lt;code&gt;core&lt;&#x2F;code&gt; are pretty big (40MBs) and deserializing them is not cheap: it takes ~127ms in a microbenchmark using &lt;code&gt;serde_json&lt;&#x2F;code&gt;. That hurts.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;optimisation-2-lazy-deserialization&quot;&gt;Optimisation #2: lazy deserialization&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s look under the cover: what&#x27;s inside the JSON docs for a crate?&lt;&#x2F;p&gt;
&lt;p&gt;The top-level JSON object looks like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;rustdoc-types&#x2F;0.22.0&#x2F;rustdoc_types&#x2F;struct.Crate.html&quot;&gt;this&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;Crate {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;root&lt;&#x2F;span&gt;&lt;span&gt;: Id,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;crate_version&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;String&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;includes_private&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;index&lt;&#x2F;span&gt;&lt;span&gt;: HashMap&amp;lt;Id, Item&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;paths&lt;&#x2F;span&gt;&lt;span&gt;: HashMap&amp;lt;Id, ItemSummary&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;external_crates&lt;&#x2F;span&gt;&lt;span&gt;: HashMap&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, ExternalCrate&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;format_version&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The biggest field is &lt;code&gt;index&lt;&#x2F;code&gt;: a map that associates a unique identifier to every single item in the crate—functions, structs, enums, trait implementations, implementation blocks, etc.&lt;br &#x2F;&gt;
We never access &lt;strong&gt;all&lt;&#x2F;strong&gt; items in the &lt;code&gt;index&lt;&#x2F;code&gt;: we only need to access the ones that are relevant to the &lt;code&gt;Blueprint&lt;&#x2F;code&gt; that we are compiling. It turns out, that&#x27;s a tiny percentage of the total number of items in the &lt;code&gt;index&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Which brings us to optimisation #2: &lt;strong&gt;lazy deserialization&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When storing the index in the SQLite cache, we don&#x27;t serialize it as a &lt;code&gt;HashMap&amp;lt;Id, Item&amp;gt;&lt;&#x2F;code&gt;. We serialize it as a combination of two columns: &lt;code&gt;items&lt;&#x2F;code&gt; and &lt;code&gt;items_id2delimiters&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
&lt;code&gt;items&lt;&#x2F;code&gt; is just a byte buffer, a &lt;code&gt;BLOB&lt;&#x2F;code&gt; column: we iterate over the values in &lt;code&gt;index&lt;&#x2F;code&gt;, serialize them and concatenate their serialized representation into a single byte buffer.&lt;br &#x2F;&gt;
&lt;code&gt;items_id2delimiters&lt;&#x2F;code&gt;, instead, is our lookup table: for each &lt;code&gt;Id&lt;&#x2F;code&gt; in the original &lt;code&gt;index&lt;&#x2F;code&gt; &lt;code&gt;HashMap&lt;&#x2F;code&gt;, it stores the offset in the &lt;code&gt;items&lt;&#x2F;code&gt; buffer where the serialized representation of the corresponding &lt;code&gt;Item&lt;&#x2F;code&gt; starts.&lt;&#x2F;p&gt;
&lt;p&gt;When loading the JSON docs for a crate from the cache, we don&#x27;t perform any deserialization of the &lt;code&gt;items&lt;&#x2F;code&gt; column. We just load the bytes and store them in a &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
When we need to access an item with a certain &lt;code&gt;Id&lt;&#x2F;code&gt; from the &lt;code&gt;index&lt;&#x2F;code&gt;, we use the &lt;code&gt;items_id2delimiters&lt;&#x2F;code&gt; lookup table to determine the offset in the &lt;code&gt;items&lt;&#x2F;code&gt; buffer and then &lt;strong&gt;pay the deserialization cost just for that item&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hyperfine&lt;&#x2F;code&gt; confirms our intuition: the execution time of &lt;code&gt;cargo px check&lt;&#x2F;code&gt; is down to &lt;strong&gt;1.417s ±  0.013s&lt;&#x2F;strong&gt;, a &lt;strong&gt;1.2x speedup&lt;&#x2F;strong&gt; over the previous iteration.&lt;br &#x2F;&gt;
We can also see it in the trace:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;a href=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_03.png&quot; target=&quot;_blank&quot; &gt;
        &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_03.png&quot; alt=&quot;Trace data visualised in the Perfetto UI&quot;&gt;
    &lt;&#x2F;a&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;Click on the image to open it in a new tab and zoom in.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The &lt;code&gt;get_toolchain_crate&lt;&#x2F;code&gt; span for &lt;code&gt;core&lt;&#x2F;code&gt; is now gone, and the corresponding retrieval from the SQLite cache takes a lot less (18ms).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;optimisation-3-don-t-touch-the-disk-if-you-don-t-have-to&quot;&gt;Optimisation #3: don&#x27;t touch the disk if you don&#x27;t have to&lt;&#x2F;h3&gt;
&lt;p&gt;Looking at the trace, I noticed how the time spent inside Pavex&#x27;s CLI now totals to ~0.7s. At the same time, &lt;code&gt;hyperfine&lt;&#x2F;code&gt; is reporting an execution time of ~1.4s.&lt;br &#x2F;&gt;
Where are the other ~0.7s going?&lt;&#x2F;p&gt;
&lt;p&gt;With the help of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mstange&#x2F;samply&quot;&gt;&lt;code&gt;samply&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, I managed to capture a flamegraph of the execution of &lt;code&gt;cargo px check&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;a href=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;flamegraph.png&quot; target=&quot;_blank&quot; &gt;
        &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;flamegraph.png&quot; alt=&quot;A sampled execution&quot;&gt;
    &lt;&#x2F;a&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;Click on the image to open it in a new tab and zoom in.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;We are spending some time at the beginning in &lt;code&gt;cargo-px&lt;&#x2F;code&gt;, primarily to execute &lt;code&gt;cargo metadata&lt;&#x2F;code&gt;. A tax that we pay again at the beginning of the execution of Pavex&#x27;s CLI.&lt;br &#x2F;&gt;
What&#x27;s interesting though, is the time spent in &lt;code&gt;cargo&lt;&#x2F;code&gt; again after &lt;code&gt;pavex_cli&lt;&#x2F;code&gt; has finished its execution. Since we are running a benchmark, the generated code is always the same and we &lt;em&gt;should not&lt;&#x2F;em&gt; need to recompile it.&lt;br &#x2F;&gt;
As it turns out, &lt;code&gt;cargo&lt;&#x2F;code&gt; takes into account the last modification time of the source file for local crates when determining if it needs to recompile them. &lt;code&gt;pavex_cli&lt;&#x2F;code&gt; always writes to disk the generated crate, even if it is unchanged, which in turn changes the &lt;code&gt;mtime&lt;&#x2F;code&gt; of the source files and triggers an unnecessary recompilation.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is easy: only write to disk the generated files if they have changed, using a checksum.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;hyperfine&lt;&#x2F;code&gt; confirms the win: the execution time of &lt;code&gt;cargo px check&lt;&#x2F;code&gt; is down to &lt;strong&gt;1.196s ± 0.006s&lt;&#x2F;strong&gt;, another &lt;strong&gt;1.2x speedup&lt;&#x2F;strong&gt;. It&#x27;s not much, but it&#x27;s honest work and a very simple change.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;optimisation-4-memoise-the-list-of-crates-we-need&quot;&gt;Optimisation #4: memoise the list of crates we need&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s go back to the last trace:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;a href=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_03.png&quot; target=&quot;_blank&quot; &gt;
        &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_03.png&quot; alt=&quot;Trace data visualised in the Perfetto UI&quot;&gt;
    &lt;&#x2F;a&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;Click on the image to open it in a new tab and zoom in.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The big issue here is that we need to compute the JSON docs for two different crates (&lt;code&gt;conduit_core&lt;&#x2F;code&gt; and &lt;code&gt;pavex_runtime&lt;&#x2F;code&gt;) and we are doing it &lt;strong&gt;serially&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
It&#x27;d be ideal to do it in &lt;strong&gt;parallel&lt;&#x2F;strong&gt;, leveraging all the cores of the machine we are running on.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s two obstacles in our way:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cargo rustdoc&lt;&#x2F;code&gt; cannot compute the JSON docs for more than one crate at a time&lt;&#x2F;li&gt;
&lt;li&gt;We need to know the list of crates that we need to compute the JSON docs for ahead of time&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The first obstacle can be solved by using &lt;code&gt;cargo doc&lt;&#x2F;code&gt; and the &lt;code&gt;RUSTDOCFLAGS&lt;&#x2F;code&gt; environment variable, as @jyn suggested to me &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rust-lang.zulipchat.com&#x2F;#narrow&#x2F;stream&#x2F;266220-rustdoc&#x2F;topic&#x2F;JSON.20metadata.20speed&#x2F;near&#x2F;359001476&quot;&gt;on Zulip&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
The second obstacle is much harder to overcome.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s look at a request handler as an example:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(my_ui::home_page));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When processing the &lt;code&gt;Blueprint&lt;&#x2F;code&gt; for the application, Pavex doesn&#x27;t know anything about the request handler for &lt;code&gt;GET &#x2F;&lt;&#x2F;code&gt; apart from its import path, &lt;code&gt;my_ui::home_page&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
Based on that path, it knows that it&#x27;ll need to compute the JSON docs for the &lt;code&gt;my_ui&lt;&#x2F;code&gt; crate.&lt;&#x2F;p&gt;
&lt;p&gt;After having computed its JSON docs, we find out that &lt;code&gt;my_ui::home_page&lt;&#x2F;code&gt; is a function that takes a &lt;code&gt;&amp;amp;hyper::Request&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;home_page&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_req&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;hyper::Request) -&amp;gt; Result&amp;lt;HomePageResponse, HomePageError&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we need to go and compute the JSON docs for &lt;code&gt;hyper&lt;&#x2F;code&gt; as well. But we had no way of knowing that ahead of time!&lt;&#x2F;p&gt;
&lt;p&gt;I spent a few days thinking about complicated solutions to get an approximate list of the crates that will be needed ahead of any expensive analysis, but then it hit me: we have an on-disk cache, &lt;strong&gt;we can cheat&lt;&#x2F;strong&gt;!&lt;br &#x2F;&gt;
We can store in the cache the list of JSON docs that we needed to access the last time that &lt;code&gt;pavex_cli&lt;&#x2F;code&gt; was invoked for a certain project.&lt;br &#x2F;&gt;
We can then load this list the next time that &lt;code&gt;pavex_cli&lt;&#x2F;code&gt; is invoked and get &lt;strong&gt;all those docs in parallel&lt;&#x2F;strong&gt;, either by loading them from the cache or by computing them in parallel if needed.&lt;&#x2F;p&gt;
&lt;p&gt;It works like a charm: the execution time goes down to &lt;strong&gt;0.928s ± 0.002s&lt;&#x2F;strong&gt;, a further &lt;strong&gt;1.3x speedup&lt;&#x2F;strong&gt; over the previous iteration. And we have finally crossed the 1s threshold, which is a nice psychological win.&lt;&#x2F;p&gt;
&lt;p&gt;You can see the parallelism at play in the trace:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center&quot; &gt;
    &lt;a href=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_04.png&quot; target=&quot;_blank&quot; &gt;
        &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-04&#x2F;trace_04.png&quot; alt=&quot;Trace data visualised in the Perfetto UI&quot;&gt;
    &lt;&#x2F;a&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;Click on the image to open it in a new tab and zoom in.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;Seeing all those threads working in parallel makes me happy.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;optimisation-n-more-to-come&quot;&gt;Optimisation #N: more to come&lt;&#x2F;h3&gt;
&lt;p&gt;Once you&#x27;re in the mindset of optimising, it&#x27;s hard to stop.&lt;br &#x2F;&gt;
Part of the work now is upstream:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Change the way &lt;code&gt;rustdoc-types&lt;&#x2F;code&gt; serializes enums in order to unlock the usage of binary formats (see &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;pull&#x2F;111427&quot;&gt;PR&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Add an &lt;code&gt;--output-format&lt;&#x2F;code&gt; flag to &lt;code&gt;cargo rustdoc&lt;&#x2F;code&gt; and &lt;code&gt;cargo doc&lt;&#x2F;code&gt; in order to fix doc caching in &lt;code&gt;cargo&lt;&#x2F;code&gt; (see &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;cargo&#x2F;issues&#x2F;12103&quot;&gt;issue&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;...optimise &lt;code&gt;cargo metadata&lt;&#x2F;code&gt;?&lt;&#x2F;li&gt;
&lt;li&gt;...make &lt;code&gt;rustdoc&lt;&#x2F;code&gt; faster?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ll be tackling the well-defined problems first, and revisit the open-ended ones later down the line if the need arises.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll also have to keep an eye on how Pavex&#x27;s own algorithms behave when the project size increases. All the measurements we have taken so far have been on a toy example, but I want to make sure that we don&#x27;t regress when we start to use Pavex on non-toy projects.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h1&gt;
&lt;p&gt;June is going to be a busy month for me—I&#x27;ll be moving back to Italy after almost 6 years in the UK. But I&#x27;ll keep working on Pavex between one box and the other.&lt;&#x2F;p&gt;
&lt;p&gt;The focus will be on fleshing out the Realworld example, trying to cover the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;codebase.show&#x2F;projects&#x2F;realworld&quot;&gt;entire spec for the backend&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
I expect I&#x27;ll be going back and forth between the example and the framework itself, adding missing features and fixing bugs as I go.&lt;&#x2F;p&gt;
&lt;p&gt;If all goes well, I might get a chance to implement middlewares, the last missing piece of the puzzle before we can start to think about an alpha release. We&#x27;ll see where I am at the end of June.&lt;&#x2F;p&gt;
&lt;p&gt;See you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can discuss this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;13qlzrg&#x2F;pavex_a_new_rust_web_framework_report_4&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;code&gt;pavex&lt;&#x2F;code&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;smithy&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;I have borrowed the terminology from the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;awslabs&#x2F;smithy-rs&#x2F;&quot;&gt;&lt;code&gt;smithy-rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; project, which I had the pleasure to work on during my time at AWS. It works quite well to describe any system where you are generating a library that encapsulates the structure of your server application from a specification of some kind (a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;smithy.io&quot;&gt;Smithy model&lt;&#x2F;a&gt;, a &lt;code&gt;Blueprint&lt;&#x2F;code&gt; in Pavex, an OpenAPI spec, etc.)&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;metadata-exception&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;With the exception of the commands that do not need a lock on the target directory, like &lt;code&gt;cargo metadata&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;consistency&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;To be able to compare apples to apples, I have manually checked out the various commits along our optmisation journey and made sure that the Realworld example was exactly the same at each step.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;tracing-chrome&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;Courtesy of the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tracing-chrome&#x2F;latest&#x2F;tracing_chrome&#x2F;&quot;&gt;&lt;code&gt;tracing-chrome&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;in-memory-cache&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;To be specific, we need to cache it across invocations; Pavex has had an in-memory cache since the very beginning in order to avoid recomputing the same docs multiple times during a single invocation of &lt;code&gt;cargo px check&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-05-24T08:08:10.47Z&quot;&gt;May 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-03&amp;#x2F;&quot;&gt;Pavex Report #3&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-05&amp;#x2F;&quot;&gt;Pavex Report #5&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Going beyond build.rs: introducing cargo-px</title>
          <pubDate>Tue, 02 May 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/cargo-px/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/cargo-px/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/cargo-px/">&lt;h1 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt; (&lt;code&gt;cargo&lt;&#x2F;code&gt; &lt;strong&gt;P&lt;&#x2F;strong&gt;ower e&lt;strong&gt;X&lt;&#x2F;strong&gt;tensions) has just been released to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;cargo-px&quot;&gt;crates.io&lt;&#x2F;a&gt;!&lt;br &#x2F;&gt;
It is a new &lt;code&gt;cargo&lt;&#x2F;code&gt; sub-command (i.e. you invoke it as &lt;code&gt;cargo px [...]&lt;&#x2F;code&gt;), designed to augment &lt;code&gt;cargo&lt;&#x2F;code&gt;&#x27;s capabilities.&lt;&#x2F;p&gt;
&lt;p&gt;This first release is focused on code generation.&lt;br &#x2F;&gt;
&lt;code&gt;cargo&lt;&#x2F;code&gt; supports &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;build-scripts.html&quot;&gt;build scripts&lt;&#x2F;a&gt; via &lt;code&gt;build.rs&lt;&#x2F;code&gt; files, but, once you look close enough, you&#x27;ll find out that they are not flexible enough to support all usecases. In particular, &lt;code&gt;build.rs&lt;&#x2F;code&gt; files fall short when you want to generate a &lt;strong&gt;whole crate&lt;&#x2F;strong&gt; (e.g. an API client starting from an OpenAPI spec or data models starting from a set of SQL queries and a database schema).&lt;&#x2F;p&gt;
&lt;p&gt;This is where &lt;code&gt;cargo-px&lt;&#x2F;code&gt; enters into the picture: it lets you specify a crate generator in the manifest of the soon-to-be-generated crate. It then transparently invokes the code generator when necessary, assuming you route your commands through &lt;code&gt;cargo-px&lt;&#x2F;code&gt; instead of &lt;code&gt;cargo&lt;&#x2F;code&gt; (i.e. invoke &lt;code&gt;cargo px build&lt;&#x2F;code&gt; rather &lt;code&gt;cargo build&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Check out the rest of the article (and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lukeMathWalker&#x2F;cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&#x27;s README&lt;&#x2F;a&gt;) to learn more!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can comment on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;135syia&#x2F;going_beyond_buildrs_introducing_cargopx&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;table-of-contents&quot;&gt;Table of contents&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;cargo-px&#x2F;#build-script-limitations&quot;&gt;Build script limitations&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;cargo-px&#x2F;#dependencies&quot;&gt;Dependencies&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;cargo-px&#x2F;#invoking-cargo&quot;&gt;Invoking &lt;code&gt;cargo&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;cargo-px&#x2F;#the-casus-belli-pavex&quot;&gt;The casus belli: &lt;code&gt;pavex&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;cargo-px&#x2F;#cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;cargo-px&#x2F;#what-about-downstream-users&quot;&gt;What about downstream users?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;build-script-limitations&quot;&gt;Build script limitations&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s get into the specifics: what are the issues with &lt;code&gt;build.rs&lt;&#x2F;code&gt; files?&lt;br &#x2F;&gt;
I ran into two key limitations:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Dependency management is cumbersome.&lt;&#x2F;li&gt;
&lt;li&gt;Invoking most &lt;code&gt;cargo&lt;&#x2F;code&gt; commands in a build script will result in a deadlock.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;We are going to dissect both of them in the next sections.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dependencies&quot;&gt;Dependencies&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s look at a specific scenario: we want to generate some Rust code that should be included as a module of the library crate that &quot;hosts&quot; the &lt;code&gt;build.rs&lt;&#x2F;code&gt; file.
To make the example easy to visualize, we can assume that the initial project layout looks like this:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;# Before code generation
&lt;&#x2F;span&gt;&lt;span&gt;mylib&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;  src&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    lib.rs (👈 empty)
&lt;&#x2F;span&gt;&lt;span&gt;  build.rs
&lt;&#x2F;span&gt;&lt;span&gt;  Cargo.toml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;build.rs&lt;&#x2F;code&gt; script is going to create a new &lt;code&gt;generated.rs&lt;&#x2F;code&gt; file under the &lt;code&gt;src&lt;&#x2F;code&gt; directory&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#forbidden&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and re-export its items from &lt;code&gt;lib.rs&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! mylib&#x2F;src&#x2F;lib.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;generated;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Re-export all items.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub use &lt;&#x2F;span&gt;&lt;span&gt;generated::*;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;# After code generation
&lt;&#x2F;span&gt;&lt;span&gt;mylib&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;  src&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;    lib.rs (⚠️ modified)
&lt;&#x2F;span&gt;&lt;span&gt;    generated.rs (👈 new file)
&lt;&#x2F;span&gt;&lt;span&gt;  build.rs
&lt;&#x2F;span&gt;&lt;span&gt;  Cargo.toml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So far so good, right?&lt;br &#x2F;&gt;
Maybe! It depends on the code in &lt;code&gt;generated.rs&lt;&#x2F;code&gt;. Does it need any &lt;strong&gt;dependencies&lt;&#x2F;strong&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;For any statement importing items from third-party crates (e.g. &lt;code&gt;use serde::Serialize&lt;&#x2F;code&gt;) there has to be a corresponding entry in the &lt;code&gt;[dependencies]&lt;&#x2F;code&gt; section of the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; manifest of our project.&lt;br &#x2F;&gt;
If that&#x27;s not the case, &lt;code&gt;cargo&lt;&#x2F;code&gt; will fail to compile the project.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, &lt;code&gt;build.rs&lt;&#x2F;code&gt; can&#x27;t help us here.&lt;br &#x2F;&gt;
You &lt;em&gt;can&lt;&#x2F;em&gt; try to modify the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; file to add the dependencies you need, but &lt;code&gt;cargo&lt;&#x2F;code&gt; is &lt;strong&gt;not&lt;&#x2F;strong&gt; going to regenerate the lockfile (&lt;code&gt;Cargo.lock&lt;&#x2F;code&gt;) after having executed the &lt;code&gt;main&lt;&#x2F;code&gt; function in your &lt;code&gt;build.rs&lt;&#x2F;code&gt; file. The code will fail to compile the first time, even though the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; file has been modified to mention the required crates.&lt;br &#x2F;&gt;
It will start working the &lt;strong&gt;second&lt;&#x2F;strong&gt; time you try to compile the project, because &lt;code&gt;cargo&lt;&#x2F;code&gt; will pick up the new manifest and re-run its dependency resolution algorithm.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s not a great experience and it&#x27;s in fact fairly uncommon for &lt;code&gt;build.rs&lt;&#x2F;code&gt; files to modify their &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; in any way.&lt;br &#x2F;&gt;
That doesn&#x27;t stop people from wanting to generate code that requires third-party dependencies though, which is why you find instructions like this one in the documentation of projects that rely heavily on code generation:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The code generated by [redacted] has a few dependencies that you need to [manually] import into your project&#x27;s Cargo.toml.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;It sucks.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s equivalent to exposing your private modules and implementation details to the consumers of your project.&lt;br &#x2F;&gt;
Do you want to migrate from &lt;code&gt;chrono&lt;&#x2F;code&gt; to &lt;code&gt;time&lt;&#x2F;code&gt; for managing timestamp columns? Now all your users need to &lt;em&gt;manually&lt;&#x2F;em&gt; swap one dependency for the other one in the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; of their code-generated crates. The same happens if you want to bump the version of your dependencies due to breaking changes (e.g. from &lt;code&gt;0.2.x&lt;&#x2F;code&gt; to &lt;code&gt;0.3.y&lt;&#x2F;code&gt;)—perhaps it was an implementation detail, but now your users need to be (painfully) aware of it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;invoking-cargo&quot;&gt;Invoking &lt;code&gt;cargo&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;There is another corner case to be aware of when it comes to build scripts: invoking &lt;code&gt;cargo&lt;&#x2F;code&gt; commands from a build script &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;cargo&#x2F;issues&#x2F;6412&quot;&gt;can result in a deadlock&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cargo&lt;&#x2F;code&gt; commands rely on a few different &lt;strong&gt;file locks&lt;&#x2F;strong&gt; to claim exclusivity before trying to modify some of the data stored in your filesystem. In particular, &lt;code&gt;cargo&lt;&#x2F;code&gt; will try to claim a file lock before executing any command that might modify &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;guide&#x2F;build-cache.html&quot;&gt;your build cache&lt;&#x2F;a&gt; (a.k.a. &quot;target&quot; directory).&lt;&#x2F;p&gt;
&lt;p&gt;Build scripts are invoked &lt;em&gt;after&lt;&#x2F;em&gt; &lt;code&gt;cargo&lt;&#x2F;code&gt; has successfully acquired a lock over your build cache.&lt;br &#x2F;&gt;
If the build script tries to invoke a &lt;code&gt;cargo&lt;&#x2F;code&gt; command that modifies the build cache (e.g. &lt;code&gt;cargo run&lt;&#x2F;code&gt; or &lt;code&gt;cargo doc&lt;&#x2F;code&gt; or &lt;code&gt;cargo build&lt;&#x2F;code&gt;), it has no way of succeeding:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the lock is held by its parent proceess, which is not going to release it until the build script exits...&lt;&#x2F;li&gt;
&lt;li&gt;...but the build script won&#x27;t exit until it acquires the lock and completes its task.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Boom, deadlock!&lt;&#x2F;p&gt;
&lt;p&gt;As a workaround, you can change the target directory for the &lt;code&gt;cargo&lt;&#x2F;code&gt; invocations coming from your build script. Different directory = different file lock = no deadlock 🎉&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s not free though—using a separate build cache implies that you won&#x27;t be able to reuse any of the artefacts that have already been built, potentially having to recompile the entire dependency tree from scratch. Ouch.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-casus-belli-pavex&quot;&gt;The casus belli: &lt;code&gt;pavex&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;As it happens, I found myself facing these challenges for &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a soon-to-be-launched Rust web framework that I&#x27;ve been working on for a few months.&lt;br &#x2F;&gt;
The framework takes a declarative approach—you specify the requirements of your application using a &lt;code&gt;Blueprint&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;app_blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::http_client), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::extract_path), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::logger), Lifecycle::Transient);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::stream_file));
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; analyzes the &lt;code&gt;Blueprint&lt;&#x2F;code&gt; (i.e. who needs what inputs, lifecycles, etc) and performs &lt;strong&gt;compile-time dependency injection&lt;&#x2F;strong&gt;—i.e. it generates a library with all your dependencies correctly wired and a dedicated entrypoint that you can invoke to launch your API server.&lt;&#x2F;p&gt;
&lt;p&gt;That analysis step relies on the information contained in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rust-lang.github.io&#x2F;rfcs&#x2F;2963-rustdoc-json.html&quot;&gt;rustdoc&#x27;s JSON output&lt;&#x2F;a&gt;, which in turn forces us to invoke &lt;code&gt;cargo doc&lt;&#x2F;code&gt; (or &lt;code&gt;cargo rustdoc&lt;&#x2F;code&gt;) as part of the code generation step.&lt;br &#x2F;&gt;
If that was not enough, the generated library has to depend on other crates! As a bare minimum, it must depend on the framework itself, but it will also have to import all the constructors and request handlers specified by the user, which might live in other local crates or come from a third-party registry.&lt;&#x2F;p&gt;
&lt;p&gt;I spent a few days torturing &lt;code&gt;build.rs&lt;&#x2F;code&gt; files. In the end, I threw in the towel: I don&#x27;t think you can make it work using a &lt;code&gt;build.rs&lt;&#x2F;code&gt; file while providing a great developer experience.&lt;br &#x2F;&gt;
I could have switched to a &quot;proper&quot; build system (e.g. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;steveklabnik.com&#x2F;writing&#x2F;using-buck-to-build-rust-projects&quot;&gt;&lt;code&gt;buck2&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; or &lt;code&gt;bazel&lt;&#x2F;code&gt;), but forcing all users of &lt;code&gt;pavex&lt;&#x2F;code&gt; to say bye bye to &lt;code&gt;cargo&lt;&#x2F;code&gt; would be an extremely tall ask.&lt;br &#x2F;&gt;
Sad, but not undeterred, I set out to build &lt;code&gt;cargo-px&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;cargo-px&quot;&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt; is a &lt;code&gt;cargo&lt;&#x2F;code&gt; sub-command.&lt;br &#x2F;&gt;
You can install it via:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span&gt; install&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --locked&lt;&#x2F;span&gt;&lt;span&gt; cargo-px
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It is designed as a &lt;strong&gt;&lt;code&gt;cargo&lt;&#x2F;code&gt; proxy&lt;&#x2F;strong&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#better-proxy&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;: instead of invoking &lt;code&gt;cargo &amp;lt;CMD&amp;gt; &amp;lt;ARGS&amp;gt;&lt;&#x2F;code&gt;, you go for &lt;code&gt;cargo px &amp;lt;CMD&amp;gt; &amp;lt;ARGS&amp;gt;&lt;&#x2F;code&gt;.
For example, you go for &lt;code&gt;cargo px build --all-features&lt;&#x2F;code&gt; instead of &lt;code&gt;cargo build --all-features&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cargo px&lt;&#x2F;code&gt; examines your workspace.&lt;br &#x2F;&gt;
If any of your crates needs to be generated, it will invoke the respective code generators &lt;em&gt;before&lt;&#x2F;em&gt; forwarding the command and its arguments to &lt;code&gt;cargo&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This strategy is quite simple, but it solves both our problems:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The required dependencies have already been added to the respective &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; files when &lt;code&gt;cargo&lt;&#x2F;code&gt; gets started, so dependency resolution kicks in as usual;&lt;&#x2F;li&gt;
&lt;li&gt;We are not holding the file lock over the build cache, so the code generators are free to invoke &lt;code&gt;cargo&lt;&#x2F;code&gt; commands as part of their generation logic.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;But let&#x27;s get to the specifics! How do you specify that a crate should be generated by &lt;code&gt;cargo px&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;It leverages the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;cargo&#x2F;reference&#x2F;manifest.html#the-metadata-table&quot;&gt;&lt;code&gt;metadata&lt;&#x2F;code&gt; section&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
In the crate that you want to see generated, you fill in the [&lt;code&gt;package.metadata.px.generate&lt;&#x2F;code&gt;] section as follows:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span&gt;[package]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;[package.metadata.px.generate]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# The generator is a binary in the current workspace. 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# It&amp;#39;s the only generator type we support at the moment.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;generator_type &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;cargo_workspace_binary&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# The name of the binary.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;generator_name &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;bp&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# The arguments to be passed to the binary. 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# It can be omitted if there are no arguments.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;generator_args &lt;&#x2F;span&gt;&lt;span&gt;= [&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;--quiet&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;--profile&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;optimised&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt; will detect the configuration and invoke &lt;code&gt;cargo run --bin bp -- --quiet --profile=&quot;optimised&quot;&lt;&#x2F;code&gt; for you.&lt;br &#x2F;&gt;
If there are multiple crates that need to be code-generated, &lt;code&gt;cargo-px&lt;&#x2F;code&gt; will invoke the respective code-generators in an order that takes into account the dependency graph (i.e. dependencies are always code-generated before their dependents).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cargo-px&lt;&#x2F;code&gt; will also set two environment variables for the code generator:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CARGO_PX_GENERATED_PKG_MANIFEST_PATH&lt;&#x2F;code&gt;, the path to the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; file of the crate that needs to be generated;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;CARGO_PX_WORKSPACE_ROOT_DIR&lt;&#x2F;code&gt;, the path to the &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt; file that defines the current workspace (i.e. the one that contains the &lt;code&gt;[workspace]&lt;&#x2F;code&gt; section).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That&#x27;s all we have at the moment!&lt;br &#x2F;&gt;
I&#x27;ll keep experimenting with &lt;code&gt;cargo-px&lt;&#x2F;code&gt; in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pavex.dev&quot;&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, but I&#x27;m curious to find out if other projects are facing similar needs and might benefit from this solution.&lt;br &#x2F;&gt;
I&#x27;d also be happy to engage with the &lt;code&gt;cargo&lt;&#x2F;code&gt; team if there is an interest in solving some of these problems upstream. &lt;code&gt;cargo&lt;&#x2F;code&gt; sub-commands are a great tool for experimentation, but standardisation is always preferable (where possible).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-about-downstream-users&quot;&gt;What about downstream users?&lt;&#x2F;h2&gt;
&lt;p&gt;Actually, one more thing before you go!&lt;br &#x2F;&gt;
Some of you may be asking: what about downstream users? Do they need to start using &lt;code&gt;cargo-px&lt;&#x2F;code&gt; as well?&lt;&#x2F;p&gt;
&lt;p&gt;It depends!&lt;br &#x2F;&gt;
&lt;code&gt;cargo-px&lt;&#x2F;code&gt; is a tool designed for &lt;strong&gt;contributors&lt;&#x2F;strong&gt; to projects that need &quot;whole crate&quot; generation for one of the crates in their workspace.&lt;br &#x2F;&gt;
If your crate is primarily a generator, then it might make sense to ask your users to rely on &lt;code&gt;cargo-px&lt;&#x2F;code&gt; if you are running into some of the limitations I mentioned above.&lt;&#x2F;p&gt;
&lt;p&gt;But the crate that gets generated by &lt;code&gt;cargo-px&lt;&#x2F;code&gt; is a &quot;regular&quot; crate—you can build it with &lt;code&gt;cargo build&lt;&#x2F;code&gt; and simply publish it to the registry.&lt;br &#x2F;&gt;
If you are using &lt;code&gt;cargo-px&lt;&#x2F;code&gt; as part of your development process (i.e. to keep an API client up-to-date from a specification file), your users don&#x27;t need to know. As far as they are concerned, they are downloading a normal crate from the registry (i.e. the output of code generation) and everything works as usual.&lt;&#x2F;p&gt;
&lt;p&gt;This is indeed one of the key differences between &lt;code&gt;cargo-px&lt;&#x2F;code&gt; and a build script: build scripts are executed on the user&#x27;s machine. This is both a feature and a drawback:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;👍 You can tune the generated code according to the environment it is being generated from (e.g. detect which SIMD features are available or compile a C library on-the-fly for that specific architecture)&lt;&#x2F;li&gt;
&lt;li&gt;👎 Your crate takes longer to compile for your users&lt;&#x2F;li&gt;
&lt;li&gt;👎 Build scripts can execute arbitrary code at compile-time, causing both issues in terms of trust (what is that script doing?) and reproducibility (should the build script be re-run? Yes? No? When?)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Keep this in mind when evaluating your options for code generation.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can comment on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;135syia&#x2F;going_beyond_buildrs_introducing_cargopx&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;forbidden&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;You aren&#x27;t supposed to generate source files using &lt;code&gt;build.rs&lt;&#x2F;code&gt;—all the generated artefacts should live in the &lt;code&gt;OUT_DIR&lt;&#x2F;code&gt; folder. But it&#x27;s a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;135syia&#x2F;comment&#x2F;jim3o8j&#x2F;&quot;&gt;common pattern&lt;&#x2F;a&gt; since there is no proper solution for source file generation, therefore I&#x27;ve chosen to &quot;do it wrong&quot; on purpose here.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;better-proxy&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;It&#x27;d be cool if it were possible to alias &lt;code&gt;cargo&lt;&#x2F;code&gt; to &lt;code&gt;cargo px&lt;&#x2F;code&gt; in a specific workspace. I probably need to play around with shims or custom toolchains.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</description>
      </item>
      <item>
          <title>Pavex, progress report #3: nested routes and borrow checking</title>
          <pubDate>Thu, 20 Apr 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-03/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-03/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-03/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is progress report about &lt;code&gt;pavex&lt;&#x2F;code&gt;, a new Rust web framework that I have been working on. It is currently
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;early stages of development&lt;&#x2F;a&gt;, working towards its first alpha release.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; to learn more about the vision!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;personal-updates&quot;&gt;Personal updates&lt;&#x2F;h2&gt;
&lt;p&gt;April was stressful.&lt;br &#x2F;&gt;
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;algo_luca&#x2F;status&#x2F;1645888229097308161&quot;&gt;I resigned from AWS&lt;&#x2F;a&gt;, started planning my relocation (UK-&amp;gt;Italy) while finalising the details of the renovations for the apartment we bought in Italy. Pretty intense!&lt;&#x2F;p&gt;
&lt;p&gt;Nonetheless, I managed to squeeze in some time for &lt;code&gt;pavex&lt;&#x2F;code&gt;—let&#x27;s talk about the progress!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can comment this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;12s00y6&#x2F;pavex_a_new_rust_web_frameworkprogress_report_3&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#compile-time-validation-for-route-parameters&quot;&gt;Compile-time validation for route parameters&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#nesting-and-encapsulation&quot;&gt;Nesting and encapsulation&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#dealing-with-ambiguity&quot;&gt;Dealing with ambiguity&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#borrow-checking&quot;&gt;Borrow checking&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#circular-dependencies&quot;&gt;Circular dependencies&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-03&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;compile-time-validation-for-route-parameters&quot;&gt;Compile-time validation for route parameters&lt;&#x2F;h2&gt;
&lt;p&gt;In March I added support for route parameters:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `home_id` is a route parameter! 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; It will extract the corresponding segment out of incoming requests at runtime. 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; E.g. `1` for `GET &#x2F;home&#x2F;1`
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&#x2F;:home_id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::get_home));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_home&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; You can then retrieve (and automatically deserialize!) the extracted route parameters 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; in your request handler using the `RouteParams` extractor.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;params&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RouteParams&amp;lt;HomeRouteParams&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; String {
&lt;&#x2F;span&gt;&lt;span&gt;    format!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Welcome to &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, params.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.&lt;&#x2F;span&gt;&lt;span&gt;home_id)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(serde::Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;HomeRouteParams {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;home_id&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What happens if you change the route template from &lt;code&gt;&#x2F;home&#x2F;:home_id&lt;&#x2F;code&gt; to &lt;code&gt;&#x2F;home&#x2F;:id&lt;&#x2F;code&gt;?&lt;br &#x2F;&gt;
From a routing perspective, they&#x27;re absolutely equivalent: a &lt;code&gt;GET&lt;&#x2F;code&gt; request to &lt;code&gt;&#x2F;home&#x2F;1&lt;&#x2F;code&gt; will match with both!&lt;&#x2F;p&gt;
&lt;p&gt;But the request handler will fail to extract route parameters from &lt;code&gt;&#x2F;home&#x2F;:id&lt;&#x2F;code&gt; if you forget to change the field name in &lt;code&gt;HomeRouteParams&lt;&#x2F;code&gt; from &lt;code&gt;home_id&lt;&#x2F;code&gt; to &lt;code&gt;id&lt;&#x2F;code&gt;. Even worse, the failure will happen at runtime—if there are no tests for this endpoint, you might end up shipping broken code in production. I don&#x27;t like that.&lt;&#x2F;p&gt;
&lt;p&gt;A new procedural macro comes to the rescue!&lt;br &#x2F;&gt;
Instead of annotating &lt;code&gt;HomeRouteParams&lt;&#x2F;code&gt; with &lt;code&gt;#[derive(serde::Deserialize)]&lt;&#x2F;code&gt;, you can use &lt;code&gt;#[RouteParams]&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RouteParams&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;HomeRouteParams {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;home_id&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you now change &lt;code&gt;&#x2F;home&#x2F;:home_id&lt;&#x2F;code&gt; to &lt;code&gt;&#x2F;home&#x2F;:id&lt;&#x2F;code&gt;, you&#x27;ll be greeted by this error when you try to re-generate your application code:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × `app::get_home` is trying to extract route parameters using `RouteParams&amp;lt;HomeRouteParams&amp;gt;`.
&lt;&#x2F;span&gt;&lt;span&gt;  │ Every struct field in `app::HomeRouteParams` must be named after one of the route parameters 
&lt;&#x2F;span&gt;&lt;span&gt;  | that appear in `&#x2F;home&#x2F;:id`:
&lt;&#x2F;span&gt;&lt;span&gt;  │ - `id`
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │ There is no route parameter named `home_id`, but there is a struct field named
&lt;&#x2F;span&gt;&lt;span&gt;  │ `home_id` in `app::HomeRouteParams`. This is going to cause a runtime error!
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;lib.rs:43:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  43 │     ));
&lt;&#x2F;span&gt;&lt;span&gt;  │  44 │     bp.route(GET, &amp;quot;&#x2F;home&#x2F;:id&amp;quot;, f!(crate::get_home));
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                                ───────────┬──────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·             The request handler asking for `RouteParams&amp;lt;app::HomeRouteParams&amp;gt;`
&lt;&#x2F;span&gt;&lt;span&gt;  │  45 │     
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: Remove or rename the fields that do not map to a valid route parameter.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Quite cool, isn&#x27;t it?&lt;br &#x2F;&gt;
Let&#x27;s unpack how it works under the hood!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; is what ties together the route template (&lt;code&gt;&#x2F;home&#x2F;:home_id&lt;&#x2F;code&gt;) with the binding struct (&lt;code&gt;HomeRouteParams&lt;&#x2F;code&gt;). Generally speaking, we can&#x27;t make any assumptions on the deserialization logic: a developer is free to provide their own exotic implementation of &lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; for &lt;code&gt;HomeRouteParams&lt;&#x2F;code&gt;—e.g. it might be indeed looking for a route segment named &lt;code&gt;id&lt;&#x2F;code&gt; which is then bound to the &lt;code&gt;home_id&lt;&#x2F;code&gt; field.&lt;br &#x2F;&gt;
If &lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; is derived though, we &lt;strong&gt;can&lt;&#x2F;strong&gt; make assumptions: each field in the struct must be named as one of the route parameters defined in the route template. If that&#x27;s not the case, deserialization is going to fail at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;This is where &lt;code&gt;#[RouteParams]&lt;&#x2F;code&gt; comes into the picture. It does two things:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Derive &lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; for your type;&lt;&#x2F;li&gt;
&lt;li&gt;Implement &lt;code&gt;pavex_runtime::serialization::StructuralDeserialize&lt;&#x2F;code&gt; for your type.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;StructuralDeserialize&lt;&#x2F;code&gt; is a marker trait:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;StructuralDeserialize {}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It provides no functionality on its own. It&#x27;s a way for us to &lt;em&gt;tag&lt;&#x2F;em&gt; a type and say &quot;their implementation of &lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; is derived&quot;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#automatically-derived&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. The &lt;code&gt;pavex&lt;&#x2F;code&gt; compiler can then look it up!&lt;br &#x2F;&gt;
When it processes the request handlers you registered, it looks at their input parameters: is there any &lt;code&gt;RouteParams&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; in there?
If there is one, &lt;code&gt;pavex&lt;&#x2F;code&gt; checks if &lt;code&gt;T&lt;&#x2F;code&gt; implements &lt;code&gt;StructuralDeserialize&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;if it does, &lt;code&gt;pavex&lt;&#x2F;code&gt; kicks off additional checks with respect to field naming;&lt;&#x2F;li&gt;
&lt;li&gt;if it doesn&#x27;t, &lt;code&gt;pavex&lt;&#x2F;code&gt; assumes that you rolled your own implementation of &lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; and trusts that you know what you are doing.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The technique is inspired by Rust&#x27;s standard library—&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;marker&#x2F;trait.StructuralEq.html&quot;&gt;StructuralEq&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;marker&#x2F;trait.StructuralPartialEq.html&quot;&gt;StructuralPartialEq&lt;&#x2F;a&gt; play the same role for identifying derived implementation of &lt;code&gt;Eq&lt;&#x2F;code&gt; and &lt;code&gt;PartialEq&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;nesting-and-encapsulation&quot;&gt;Nesting and encapsulation&lt;&#x2F;h2&gt;
&lt;p&gt;Everything starts simple, including APIs.&lt;br &#x2F;&gt;
You can easily keep your entire router and state in a single function when you are exposing 4 or 5 endpoints. Things get really messy when, over time, the API surface grows to tens (if not hundreds!) of routes with an intricate network of dependencies and middlewares.&lt;&#x2F;p&gt;
&lt;p&gt;Our brains are limited—it&#x27;s hard to keep too many different things in mind when working on a codebase&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#programmer-brain&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. That&#x27;s what modules are for!&lt;br &#x2F;&gt;
Modules empower us to &lt;em&gt;segment&lt;&#x2F;em&gt; our domain in units that are small enough to be reasoned about, &lt;em&gt;encapsulating&lt;&#x2F;em&gt; complexity behind an interface that abstracts away the nitty-gritty details.&lt;&#x2F;p&gt;
&lt;p&gt;Last month, &lt;code&gt;pavex&lt;&#x2F;code&gt; had no mechanism for encapsulation. All routes, constructors and error handlers lived in a flat &quot;namespace&quot;. That&#x27;s optimal for a small microservice—you don&#x27;t want to pay the cognitive price of abstractions you don&#x27;t need.&lt;br &#x2F;&gt;
But I want &lt;code&gt;pavex&lt;&#x2F;code&gt; to be able to support your project as it grows in complexity—it should be the ideal foundation for building large monoliths in Rust&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#go-big&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s why I&#x27;ve added support for &lt;strong&gt;nesting&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::db_connection_pool), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;nest_at&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;admin_blueprint&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;nest_at&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;api&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;api_bp&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;admin_blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::session_token), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::admin_dashboard));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;api_blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can decompose your application into smaller &lt;code&gt;Blueprint&lt;&#x2F;code&gt;s, each focused on a subset of routes and constructors.&lt;br &#x2F;&gt;
A nested &lt;code&gt;Blueprint&lt;&#x2F;code&gt; inherits all the constructors registered against its parents: in our example, both &lt;code&gt;&#x2F;admin&#x2F;*&lt;&#x2F;code&gt; and &lt;code&gt;&#x2F;api&#x2F;*&lt;&#x2F;code&gt; request handlers can access the database connection pool returned by the top-level constructor.&lt;br &#x2F;&gt;
The opposite, instead, is forbidden: constructors registered against a nested blueprint are not visible to its parent(s) nor to its siblings. Going back to the example above, &lt;code&gt;&#x2F;api&#x2F;*&lt;&#x2F;code&gt; request handlers cannot access the session token returned by the constructor registered in &lt;code&gt;admin_blueprint&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This kind of encapsulation allows you to keep a close eye on the set of dependencies available to each part of your application.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;nest_at&lt;&#x2F;code&gt; has another side-effect: it adds a prefix to all the routes registered by the nested blueprint. &lt;code&gt;crate::admin_dashboard&lt;&#x2F;code&gt; will be invoked on &lt;code&gt;GET &#x2F;admin&#x2F;&lt;&#x2F;code&gt; requests instead of &lt;code&gt;GET &#x2F;&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
Decomposition, though, does not always map cleanly to path prefixes. That&#x27;s why &lt;code&gt;pavex&lt;&#x2F;code&gt; provides another method, &lt;code&gt;nest&lt;&#x2F;code&gt;, which has identical behaviour with respect to state encapsulation but does not add any route prefix.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dealing-with-ambiguity&quot;&gt;Dealing with ambiguity&lt;&#x2F;h3&gt;
&lt;p&gt;Nesting and encapsulation are cool on paper, but the devil is in the details.&lt;br &#x2F;&gt;
What happens if &lt;code&gt;api_blueprint&lt;&#x2F;code&gt; and &lt;code&gt;admin_blueprint&lt;&#x2F;code&gt; try to register different constructors for the same singleton type, a &lt;code&gt;u64&lt;&#x2F;code&gt;?&lt;br &#x2F;&gt;
Singletons should be... well, singletons—built once and used for the entirety of the application lifetime. Which constructor should &lt;code&gt;pavex&lt;&#x2F;code&gt; use? The one provided by &lt;code&gt;api_blueprint&lt;&#x2F;code&gt;? Or the one provided by &lt;code&gt;admin_blueprint&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;The answer is neither! This edge case is accounted for and we return a dedicated error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × The constructor for a singleton must be registered once.
&lt;&#x2F;span&gt;&lt;span&gt;  │ You registered the same constructor for `u64` against 2 different nested
&lt;&#x2F;span&gt;&lt;span&gt;  │ blueprints.
&lt;&#x2F;span&gt;&lt;span&gt;  │ I don&amp;#39;t know how to proceed: do you want to share the same singleton
&lt;&#x2F;span&gt;&lt;span&gt;  │ instance across all those nested blueprints, or do you want to create a
&lt;&#x2F;span&gt;&lt;span&gt;  │ new instance for each nested blueprint?
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;lib.rs:10:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  10 │     let mut bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  │  11 │     bp.constructor(f!(crate::admin::singleton), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                    ──────────┬───────────────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                              ╰── A constructor was registered here
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;lib.rs:22:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  22 │     let mut bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  │  23 │     bp.constructor(f!(crate::api::singleton), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                    ──────────┬─────────────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                              ╰── A constructor was registered here
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: If you want to share a single instance of `u64`, remove constructors
&lt;&#x2F;span&gt;&lt;span&gt;  │         for `u64` until there is only one left. It should be attached to a
&lt;&#x2F;span&gt;&lt;span&gt;  │         blueprint that is a parent of all the nested ones that need to use it.
&lt;&#x2F;span&gt;&lt;span&gt;  │        ☞
&lt;&#x2F;span&gt;&lt;span&gt;  │          ╭─[src&#x2F;lib.rs:5:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │        5 │ pub fn blueprint() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;  │        6 │     let mut bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  │          ·                  ────────┬───────
&lt;&#x2F;span&gt;&lt;span&gt;  │          ·                          ╰── Register your constructor against this blueprint
&lt;&#x2F;span&gt;&lt;span&gt;  │          ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: If you want different instances, consider creating separate newtypes
&lt;&#x2F;span&gt;&lt;span&gt;  │         that wrap a `u64`.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A similar reasoning applies if a nested blueprint tries to override the constructor registered by its parent for a singleton type.&lt;br &#x2F;&gt;
The approach is different, instead, for request-scoped and transient types: nested blueprints can override the behaviour of their parent—e.g. register a different error handler for the same extractor.&lt;&#x2F;p&gt;
&lt;p&gt;Striking a balance between expressiveness and the principle of least surprise is tricky. I expect that I&#x27;ll have to iterate further on this part of the API going forward, but I&#x27;m happy enough with this first version!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;borrow-checking&quot;&gt;Borrow checking&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; is a code generator—it takes as input a &lt;code&gt;Blueprint&lt;&#x2F;code&gt; that describes your application and spits out Rust code that can serve incoming requests.&lt;br &#x2F;&gt;
There is a key detail here: the Rust code that we generate &lt;strong&gt;must compile successfully&lt;&#x2F;strong&gt;, which in turn implies that &lt;strong&gt;it must satisfy the Rust borrow checker&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s trickier than it sounds—it might or might not be possible to generate code that makes the borrow checker happy, depending on the shape of your dependency graph. Let&#x27;s look at an example:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center;&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-03&#x2F;triangle.svg&quot; alt=&quot;A problematic call graph&quot;&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;A problematic call graph.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;To invoke &lt;code&gt;request_handler&lt;&#x2F;code&gt;, we need to build an instance of &lt;code&gt;B&lt;&#x2F;code&gt; and an instance of &lt;code&gt;C&lt;&#x2F;code&gt;. But their respective constructors want to take &lt;code&gt;A&lt;&#x2F;code&gt; as input &lt;strong&gt;by value&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
That can&#x27;t be—the borrow checker would reject the resulting code.&lt;&#x2F;p&gt;
&lt;p&gt;Last month, that&#x27;s exactly what used to happen: &lt;code&gt;pavex&lt;&#x2F;code&gt; would happily accept your &lt;code&gt;Blueprint&lt;&#x2F;code&gt; and then emit code that didn&#x27;t compile. Understanding &lt;em&gt;why&lt;&#x2F;em&gt; it didn&#x27;t compile (and mapping it back to your registered constructors) was left as an exercise for the user.&lt;&#x2F;p&gt;
&lt;p&gt;That sucks, and I spent the better part of April fixing it.&lt;br &#x2F;&gt;
If you try to pass a similar call graph to &lt;code&gt;pavex&lt;&#x2F;code&gt; today, it gets rejected with an error:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × I can&amp;#39;t generate code that will pass the borrow checker *and* match the
&lt;&#x2F;span&gt;&lt;span&gt;  │ instructions in your blueprint.
&lt;&#x2F;span&gt;&lt;span&gt;  │ There are 2 components that take `app::A` as an input parameter, consuming
&lt;&#x2F;span&gt;&lt;span&gt;  │ it by value. Since I&amp;#39;m not allowed to clone `app::A`, I can&amp;#39;t resolve
&lt;&#x2F;span&gt;&lt;span&gt;  │ this conflict.
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: Allow me to clone `app::A` in order to satisfy the borrow checker.
&lt;&#x2F;span&gt;&lt;span&gt;  │         You can do so by invoking `.cloning(CloningStrategy::CloneIfNecessary)`
&lt;&#x2F;span&gt;&lt;span&gt;  │         on the type returned by `.constructor`.
&lt;&#x2F;span&gt;&lt;span&gt;  │        ☞
&lt;&#x2F;span&gt;&lt;span&gt;  │           ╭─[src&#x2F;lib.rs:40:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │        40 │     let mut bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  │        41 │     bp.constructor(f!(crate::build_a), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;  │           ·                    ──────┬──────────
&lt;&#x2F;span&gt;&lt;span&gt;  │           ·                          ╰── The constructor was registered here
&lt;&#x2F;span&gt;&lt;span&gt;  │           ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: Considering changing the signature of the components that consume
&lt;&#x2F;span&gt;&lt;span&gt;  │         `app::A` by value.
&lt;&#x2F;span&gt;&lt;span&gt;  │         Would a shared reference, `&amp;amp;app::A`, be enough?
&lt;&#x2F;span&gt;&lt;span&gt;  │        ☞
&lt;&#x2F;span&gt;&lt;span&gt;  │           ╭─[src&#x2F;lib.rs:42:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │        42 │     bp.constructor(f!(crate::build_b), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;  │        43 │     bp.constructor(f!(crate::build_c), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;  │           ·                    ──────┬──────────
&lt;&#x2F;span&gt;&lt;span&gt;  │           ·                          ╰── One of the consuming constructors
&lt;&#x2F;span&gt;&lt;span&gt;  │           ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │        ☞
&lt;&#x2F;span&gt;&lt;span&gt;  │           ╭─[src&#x2F;lib.rs:41:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │        41 │     bp.constructor(f!(crate::build_a), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;  │        42 │     bp.constructor(f!(crate::build_b), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;  │           ·                    ──────┬──────────
&lt;&#x2F;span&gt;&lt;span&gt;  │           ·                          ╰── One of the consuming constructors
&lt;&#x2F;span&gt;&lt;span&gt;  │           ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: If `app::A` itself cannot implement `Clone`, consider wrapping it in
&lt;&#x2F;span&gt;&lt;span&gt;  │         an `std::sync::Rc` or `std::sync::Arc`.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The borrow checker is a tricky beast on its own, so I put in a lot of effort in suggesting possible remediations.&lt;br &#x2F;&gt;
The first is what I&#x27;d generally recommend: just &lt;code&gt;Clone&lt;&#x2F;code&gt; it!&lt;&#x2F;p&gt;
&lt;p&gt;By default, &lt;code&gt;pavex&lt;&#x2F;code&gt; doesn&#x27;t inject &lt;code&gt;.clone()&lt;&#x2F;code&gt; invocations. You need to explicitly tell the framework that it&#x27;s OK to clone a type if needed:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::build_a), Lifecycle::RequestScoped)
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 👇 This allows `pavex` to sprinkle in `.clone()` calls where helpful
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;cloning&lt;&#x2F;span&gt;&lt;span&gt;(CloningStrategy::CloneIfNecessary);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That change is enough to fix the previous error—the call graph becomes:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center;&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-03&#x2F;triangle_with_clone.svg&quot; alt=&quot;The fixed call graph&quot;&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;The fixed call graph.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt;&#x27;s code generation is then smart enough to process the &lt;code&gt;Clone::clone()&lt;&#x2F;code&gt; node before invoking &lt;code&gt;build_b&lt;&#x2F;code&gt;, therefore producing code that passes the borrow checker 🎉&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s be clear: &lt;code&gt;pavex&lt;&#x2F;code&gt; does not yet catch &lt;strong&gt;all&lt;&#x2F;strong&gt; possible borrow-checking issues ahead of code generation, but it does a fairly good job at catching the most common violations (e.g. borrow after moved) as well as some of the trickier ones (e.g. when control flow statements like &lt;code&gt;match&lt;&#x2F;code&gt; are involved).&lt;br &#x2F;&gt;
Its main blindspots are &quot;hidden&quot; borrows—e.g. &lt;code&gt;C&lt;&#x2F;code&gt; depends on &lt;code&gt;B&amp;lt;&#x27;a&amp;gt;&lt;&#x2F;code&gt;, which stores a reference to &lt;code&gt;A&lt;&#x2F;code&gt; as one of its fields, therefore implying that &lt;code&gt;C&lt;&#x2F;code&gt; borrows from &lt;code&gt;A&lt;&#x2F;code&gt;. It can be solved, there is no hard blocker there—it&#x27;s just a matter of putting in the work, something I plan to tackle in the mid-future.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;circular-dependencies&quot;&gt;Circular dependencies&lt;&#x2F;h2&gt;
&lt;p&gt;Last but not least, I&#x27;ve done some bug squashing!&lt;br &#x2F;&gt;
&lt;code&gt;pavex&lt;&#x2F;code&gt; doesn&#x27;t like circular dependencies, like in this call graph:&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center;&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;pavex-report-03&#x2F;cycle.svg&quot; alt=&quot;A graph with circular dependencies&quot;&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;A graph with circular dependencies.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;It used to handle circular dependencies very poorly—it would hang, indefinitely, stuck in an infinite loop.
I have introduced an intermediate analysis step (called &lt;code&gt;DependencyGraph&lt;&#x2F;code&gt;) to detect circular dependencies before they become an existential problem, removing the infinite loop and emitting a nice error as a result:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × The dependency graph cannot contain cycles, but I just found one!
&lt;&#x2F;span&gt;&lt;span&gt;  │ If I tried to build your dependencies, I would end up in an infinite loop.
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │ The cycle looks like this:
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │ - `build_b` depends on `app::C`, which is built by `build_c`
&lt;&#x2F;span&gt;&lt;span&gt;  │ - `build_c` depends on `app::A`, which is built by `build_a`
&lt;&#x2F;span&gt;&lt;span&gt;  │ - `build_a` depends on `app::B`, which is built by `build_b`
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: Break the cycle! Remove one of the &amp;#39;depends-on&amp;#39; relationship by
&lt;&#x2F;span&gt;&lt;span&gt;  │         changing the signature of one of the components in the cycle.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h1&gt;
&lt;p&gt;First and foremost, some rest! I&#x27;ll be off the grid for a few days, taking a little break.&lt;&#x2F;p&gt;
&lt;p&gt;Speaking of &lt;code&gt;pavex&lt;&#x2F;code&gt;, there is one &lt;strong&gt;key&lt;&#x2F;strong&gt; feature that I&#x27;ve yet to implement: middlewares.&lt;br &#x2F;&gt;
But they&#x27;ll have to wait a bit longer. I am eager to kick the tires on &lt;code&gt;pavex&lt;&#x2F;code&gt;—i.e. try to build a small project to see how it &lt;strong&gt;feels&lt;&#x2F;strong&gt; to develop with &lt;code&gt;pavex&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll probably be implementing the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.realworld.how&#x2F;&quot;&gt;Realworld API spec&lt;&#x2F;a&gt;—I&#x27;ve done it in the past using &lt;code&gt;actix-web&lt;&#x2F;code&gt; and it should give me a pretty solid measure of what needs to be done next for &lt;code&gt;pavex&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
As a bonus, it&#x27;ll help me to validate the design sketches for the middleware API. I have plenty of crazy-man notes spread around the house, full of boxes and arrows.&lt;&#x2F;p&gt;
&lt;p&gt;See you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can comment this update on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;12s00y6&#x2F;pavex_a_new_rust_web_frameworkprogress_report_3&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;code&gt;pavex&lt;&#x2F;code&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;automatically-derived&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;As it happens, I found out a couple of days ago that there &lt;em&gt;might&lt;&#x2F;em&gt; be &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;adotinthevoid&#x2F;status&#x2F;1647627584534872064&quot;&gt;a way to determine if you derived &lt;code&gt;serde::Deserialize&lt;&#x2F;code&gt; without having to introduce a marker trait&lt;&#x2F;a&gt;. I&#x27;ll investigate it further in the near future.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;programmer-brain&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;If the intersection of neuroscience and developer experience fascinates you, I strongly recommend checking out &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.manning.com&#x2F;books&#x2F;the-programmers-brain&quot;&gt;The Programmer&#x27;s brain&lt;&#x2F;a&gt; by &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;Felienne&quot;&gt;Felienne Hermans&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;go-big&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;Monoliths have a bad reputation, but they can be surprisingly effective &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;m.signalvnoise.com&#x2F;the-majestic-monolith&#x2F;&quot;&gt;in the right circumstances&lt;&#x2F;a&gt;. As an industry, we often think in absolutes—&quot;Monolith? A gigantic spaghetti mess deployed on one big box&quot;—reality is more nuanced. Powered by the right framework, it should be easy enough to deploy a monolithic application as a set of serverless functions, one for each endpoint. As long as they don&#x27;t call into each other, you retain most of the benefits of a &quot;traditional&quot; monolith without many of its scalability&#x2F;billing downsides. Food for thought—hybrid deployment strategies are definitely top of mind for me when thinking about &lt;code&gt;pavex&lt;&#x2F;code&gt;&#x27;s future directions.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-04-20T08:08:10.47Z&quot;&gt;April 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-02&amp;#x2F;&quot;&gt;Pavex Report #2&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-04&amp;#x2F;&quot;&gt;Pavex Report #4&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Pavex, progress report #2: route all the things</title>
          <pubDate>Wed, 22 Mar 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-02/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-02/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-02/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is a progress report about &lt;code&gt;pavex&lt;&#x2F;code&gt;, a new Rust web framework that I have been working on. It is currently
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;early stages of development&lt;&#x2F;a&gt;, working towards its first alpha release.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; to learn more about the vision!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;March is coming to an end: time for another progress report on &lt;code&gt;pavex&lt;&#x2F;code&gt;!
&lt;a href=&quot;&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;&quot;&gt;The previous update&lt;&#x2F;a&gt; ended with an outline of the work I wanted to pick up in March:
giving &lt;code&gt;pavex&lt;&#x2F;code&gt; a request router worth of its ambitions.&lt;&#x2F;p&gt;
&lt;p&gt;The existing implementation was an extremely basic placeholder:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::my_handler), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You could not specify an HTTP method, nor a templated path segment (e.g. &lt;code&gt;&#x2F;users&#x2F;:id&lt;&#x2F;code&gt;) and the generated application
would panic if sent a request to a path that did not match any of the registered handlers.&lt;br &#x2F;&gt;
That was not going to cut it!&lt;&#x2F;p&gt;
&lt;p&gt;After several weeks of work, we are now much closer to what I had in mind:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_builder::{f, router::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, Blueprint, Lifecycle};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The handler will only be invoked if the request method is `GET`.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We are also registering a route parameter, `home_id`..
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&#x2F;:home_id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::get_home));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ..which we can retrieve in the request handler, using the `RouteParams` extractor.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_home&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;params&lt;&#x2F;span&gt;&lt;span&gt;: RouteParams&amp;lt;HomeRouteParams&amp;gt;) -&amp;gt; String {
&lt;&#x2F;span&gt;&lt;span&gt;    format!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Welcome to &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, params.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.&lt;&#x2F;span&gt;&lt;span&gt;home_id)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(serde::Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;HomeRouteParams {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;home_id&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But the API on its own does not tell the whole story! Let&#x27;s dive into the details!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#method-guards&quot;&gt;Method guards&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#route-parameters&quot;&gt;Route parameters&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#no-tuples-please&quot;&gt;No tuples, please!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#unroutable-requests&quot;&gt;Unroutable requests&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#better-foundations&quot;&gt;Better foundations&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-02&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;method-guards&quot;&gt;Method guards&lt;&#x2F;h2&gt;
&lt;p&gt;As you have seen in the overview example, &lt;code&gt;pavex&lt;&#x2F;code&gt; now support specifying &lt;em&gt;method guards&lt;&#x2F;em&gt; on routes—i.e. invoke the
request handler only if the request method matches the one specified in the route registration.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_builder::{f, router::*, Blueprint};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_runtime::http::Method;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Single method guards, for all the well-known HTTP methods.
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;get&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handler));
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;post&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handler));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ..as well as PUT, CONNECT, DELETE, HEAD, OPTIONS, PATCH, and TRACE. 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; You can still match a path regardless of HTTP method, using the `ANY` guard.
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ANY&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;any&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handler));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Or you can specify multiple methods, building your own `MethodGuard` type.
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        MethodGuard::new([Method::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;PATCH&lt;&#x2F;span&gt;&lt;span&gt;, Method::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;]),
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;mixed&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handler),
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So far, not particularly exciting—you&#x27;ve most likely seen a very similar API in other web frameworks.&lt;br &#x2F;&gt;
Things get interesting when you make a mistake—for example, try to register two different request handlers
for the same path-method combination:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ANY&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handler_1));
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handler_2));
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Overlapping routes may look like a minor annoyance, but they can be a source of subtle bugs in large
applications, where routes are being registered all over the place. Even more so if the web framework
chooses to silently &quot;resolve&quot; the conflict by picking one of the handlers according to some obscure set of route
precedence rules.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; takes a &quot;fail fast&quot; stance:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;  × I don&amp;#39;t know how to route incoming `GET &#x2F;home` requests: you have
&lt;&#x2F;span&gt;&lt;span&gt;  │ registered 2 different request handlers for this path+method combination.
&lt;&#x2F;span&gt;&lt;span&gt;  │
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;lib.rs:16:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  16 │     let mut bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  │  17 │     bp.route(ANY, &amp;quot;&#x2F;home&amp;quot;, f!(crate::handler_1));
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                            ──────────┬─────────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                                      ╰── The first conflicting handler
&lt;&#x2F;span&gt;&lt;span&gt;  │  18 │     bp.route(GET, &amp;quot;&#x2F;home&amp;quot;, f!(crate::handler_2));
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   ×
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╭─[src&#x2F;lib.rs:17:1]
&lt;&#x2F;span&gt;&lt;span&gt;  │  17 │     bp.route(ANY, &amp;quot;&#x2F;home&amp;quot;, f!(crate::handler_1));
&lt;&#x2F;span&gt;&lt;span&gt;  │  18 │     bp.route(GET, &amp;quot;&#x2F;home&amp;quot;, f!(crate::handler_2));
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                            ──────────┬─────────
&lt;&#x2F;span&gt;&lt;span&gt;  │     ·                                      ╰── The second conflicting handler
&lt;&#x2F;span&gt;&lt;span&gt;  │  19 │     bp
&lt;&#x2F;span&gt;&lt;span&gt;  │     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  │   help: You can only register one request handler for each path+method
&lt;&#x2F;span&gt;&lt;span&gt;  │         combination. Remove all but one of the conflicting request handlers.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It examines all registered routes ahead of code generation, detects a conflict and returns you an error message
explaining what is wrong and where. I think that&#x27;s pretty neat!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;route-parameters&quot;&gt;Route parameters&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s talk about route parameters!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&#x2F;:home_id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::get_home));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can specify a route parameter by prefixing the path segment with a colon (&lt;code&gt;:&lt;&#x2F;code&gt;).&lt;br &#x2F;&gt;
The syntax should look familiar: &lt;code&gt;pavex&lt;&#x2F;code&gt;&#x27;s routing is built on top of the excellent &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;matchit&#x2F;latest&#x2F;matchit&#x2F;&quot;&gt;&lt;code&gt;matchit&lt;&#x2F;code&gt; crate&lt;&#x2F;a&gt;
by &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ibraheemdev&quot;&gt;@ibraheemdev&lt;&#x2F;a&gt;, the same crate used under the hood by &lt;code&gt;axum&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Route parameters are used to &lt;strong&gt;bind&lt;&#x2F;strong&gt; segments in the URL of an incoming request to a name (&lt;code&gt;home_id&lt;&#x2F;code&gt;, in our example).
The same name is then used to retrieve the bound values in the corresponding request handler.&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;code&gt;pavex&lt;&#x2F;code&gt;, you can use the &lt;code&gt;RawRouteParams&lt;&#x2F;code&gt; extractor to access the &lt;strong&gt;raw&lt;&#x2F;strong&gt; values that have been bound to
each route parameter:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_home&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;params&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RawRouteParams) -&amp;gt; String {
&lt;&#x2F;span&gt;&lt;span&gt;    format!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Welcome to &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, params.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;home_id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;RawRouteParams&lt;&#x2F;code&gt; extractor is a thin wrapper around the &lt;code&gt;matchit::Params&lt;&#x2F;code&gt; type, which is a &lt;code&gt;HashMap&lt;&#x2F;code&gt;-like
data structure that associates route parameter names to their raw values.&lt;br &#x2F;&gt;
Having &lt;code&gt;RawRouteParams&lt;&#x2F;code&gt; around is handy, but it&#x27;s too low-level for most use cases. That&#x27;s where
&lt;code&gt;RouteParams&lt;&#x2F;code&gt; comes in!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_home&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;params&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RouteParams&amp;lt;HomeRouteParams&amp;gt;) -&amp;gt; String {
&lt;&#x2F;span&gt;&lt;span&gt;    format!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Welcome to &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, params.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.&lt;&#x2F;span&gt;&lt;span&gt;home_id)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(serde::Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;HomeRouteParams {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;home_id&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It percent-decodes all the extracted parameters and then tries to deserialize them according to the type
you have specified—e.g. &lt;code&gt;u32&lt;&#x2F;code&gt; for &lt;code&gt;home_id&lt;&#x2F;code&gt; in our example.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;no-tuples-please&quot;&gt;No tuples, please!&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;RouteParams&lt;&#x2F;code&gt; is where &lt;code&gt;pavex&lt;&#x2F;code&gt; diverges from the approach of other Rust web frameworks: the &lt;code&gt;T&lt;&#x2F;code&gt; in &lt;code&gt;RouteParams&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;
&lt;strong&gt;must&lt;&#x2F;strong&gt; be a plain struct with named fields.&lt;br &#x2F;&gt;
We do not allow tuples, tuple structs or vectors. Only plain structs with named fields.&lt;br &#x2F;&gt;
This is a deliberate design choice: &lt;code&gt;pavex&lt;&#x2F;code&gt; strives to enable &lt;strong&gt;local reasoning&lt;&#x2F;strong&gt;, whenever the tradeoff makes sense.&lt;br &#x2F;&gt;
It should be easy to understand what each extracted route parameter represents without having to jump back and forth
between multiple files.&lt;br &#x2F;&gt;
Structs with named fields are ideal in this regard: by looking at the field name you can immediately understand which
route parameter is being extracted—they are &lt;strong&gt;self-documenting&lt;&#x2F;strong&gt;. The same is not true for tuples—e.g.
&lt;code&gt;(String, u64, u32)&lt;&#x2F;code&gt;—where you have to go and check the route’s template to understand what each entry represents.&lt;&#x2F;p&gt;
&lt;p&gt;I anticipate that many people will expect tuples to be supported in &lt;code&gt;pavex&lt;&#x2F;code&gt; just as they are in other Rust web
frameworks, therefore I made an effort to catch incorrect usage at compile time and provide a helpful error message.
For example, if you try to use a tuple:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Blueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = Blueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&#x2F;:home_id&#x2F;room&#x2F;:room_id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::get_room));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_room&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;params&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;RouteParams&amp;lt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;)&amp;gt;) -&amp;gt; String {
&lt;&#x2F;span&gt;&lt;span&gt;    format!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Welcome to &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, params.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.&lt;&#x2F;span&gt;&lt;span&gt;home_id)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You&#x27;ll be greeted by this error:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-ERROR:
&lt;&#x2F;span&gt;&lt;span&gt;× Route parameters must be extracted using a plain struct with named fields,
&lt;&#x2F;span&gt;&lt;span&gt;│ where the name of each field matches one of the route parameters specified
&lt;&#x2F;span&gt;&lt;span&gt;│ in the route for the respective request handler.
&lt;&#x2F;span&gt;&lt;span&gt;│ `app::get_room` is trying to extract `RouteParams&amp;lt;(u32, u32)&amp;gt;`, but `(u32,
&lt;&#x2F;span&gt;&lt;span&gt;│ u32)` is a tuple, not a plain struct type. I don&amp;#39;t support this: the
&lt;&#x2F;span&gt;&lt;span&gt;│ extraction would fail at runtime, when trying to process an incoming
&lt;&#x2F;span&gt;&lt;span&gt;│ request.
&lt;&#x2F;span&gt;&lt;span&gt;│
&lt;&#x2F;span&gt;&lt;span&gt;│     ╭─[src&#x2F;lib.rs:56:1]
&lt;&#x2F;span&gt;&lt;span&gt;│  57 │     bp.route(GET, &amp;quot;&#x2F;home&#x2F;:home_id&#x2F;room&#x2F;:room_id&amp;quot;, f!(crate::get_room));
&lt;&#x2F;span&gt;&lt;span&gt;│     ·                                                   ────────┬──────────
&lt;&#x2F;span&gt;&lt;span&gt;│     ·                                 The request handler asking for `RouteParams&amp;lt;(u32, u32)&amp;gt;`
&lt;&#x2F;span&gt;&lt;span&gt;│  58 │     &#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;│     ╰────
&lt;&#x2F;span&gt;&lt;span&gt;│   help: Use a plain struct with named fields to extract route parameters.
&lt;&#x2F;span&gt;&lt;span&gt;│         Check out `RouteParams`&amp;#39; documentation for all the details!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I could make it even better by sketching out what the struct should look like in the &lt;code&gt;help&lt;&#x2F;code&gt; message, but that&#x27;ll
require a bit more work on the error message formatting side of things. One more item in the backlog!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unroutable-requests&quot;&gt;Unroutable requests&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; will now behave as you&#x27;d expect when it receives a request for a route that does not have a registered
request handler:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It returns a &lt;code&gt;404 Not Found&lt;&#x2F;code&gt; status code if there is no request handler registered for the requested path.&lt;&#x2F;li&gt;
&lt;li&gt;It returns a &lt;code&gt;405 Method Not Allowed&lt;&#x2F;code&gt; status code if there is at least one request handler registered for the same
path, but with a different HTTP method. E.g. you have a &lt;code&gt;GET &#x2F;home&lt;&#x2F;code&gt; handler, but you sent a &lt;code&gt;POST &#x2F;home&lt;&#x2F;code&gt; request.
The response includes an &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTTP&#x2F;Headers&#x2F;Allow&quot;&gt;&lt;code&gt;Allow&lt;&#x2F;code&gt; header&lt;&#x2F;a&gt; with the
list of allowed methods for the requested path.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;better-foundations&quot;&gt;Better foundations&lt;&#x2F;h2&gt;
&lt;p&gt;As the framework expands, I am constantly refactoring the internals to refine our abstractions
and expand the capabilities of our compile-time reflection engine.&lt;br &#x2F;&gt;
During this iteration I&#x27;ve added preliminary support in the reflection engine for:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;generic types;&lt;&#x2F;li&gt;
&lt;li&gt;non-&lt;code&gt;&#x27;static&lt;&#x2F;code&gt; lifetime parameters;&lt;&#x2F;li&gt;
&lt;li&gt;non-static methods (i.e. methods that take &lt;code&gt;&amp;amp;self&lt;&#x2F;code&gt; as the first parameter).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;code&gt;RouteParams::extract&lt;&#x2F;code&gt; required 1., while 2. was necessary for &lt;code&gt;RawRouteParams&amp;lt;&#x27;server, &#x27;request&amp;gt;&lt;&#x2F;code&gt;—we perform zero-copy
deserialization of route parameters where possible (i.e. when the parameter value is not URL-encoded), which requires
being able to borrow from the incoming request (which does not live for &lt;code&gt;&#x27;static&lt;&#x2F;code&gt;).&lt;br &#x2F;&gt;
With 1. and 2. in place, 3. was a fairly straightforward addition.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h1&gt;
&lt;p&gt;The router is definitely in a much better shape now, but we are not done yet!&lt;br &#x2F;&gt;
There a few more features I&#x27;d like to add before moving on:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Scopes: a way to group routes together and apply a common prefix to all of them.&lt;&#x2F;li&gt;
&lt;li&gt;Compile-time parameter validation: we should be able to verify that all fields in the &lt;code&gt;RouteParams&lt;&#x2F;code&gt; type are
actually present in the route template.&lt;&#x2F;li&gt;
&lt;li&gt;Compile-time detection of common pitfalls—e.g. using &lt;code&gt;&amp;amp;str&lt;&#x2F;code&gt; instead of &lt;code&gt;Cow&amp;lt;&#x27;_, str&amp;gt;&lt;&#x2F;code&gt; for route parameters, which
will cause a runtime panic if the parameter value is URL-encoded.&lt;&#x2F;li&gt;
&lt;li&gt;A &lt;code&gt;MatchedRoute&lt;&#x2F;code&gt; extractor that provides access to the template that has been matched for the incoming request,
primarily useful for logging purposes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The foundations for some of these features are already in place, so I don&#x27;t expect major blockers in getting them over
the line—&quot;just&quot; a matter of putting in the work.&lt;&#x2F;p&gt;
&lt;p&gt;See you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;code&gt;pavex&lt;&#x2F;code&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-03-22T08:08:10.47Z&quot;&gt;March 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-01&amp;#x2F;&quot;&gt;Pavex Report #1&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-03&amp;#x2F;&quot;&gt;Pavex Report #3&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>Pavex, progress report #1: laying the foundations</title>
          <pubDate>Tue, 14 Feb 2023 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-01/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-01/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/pavex-progress-report-01/">&lt;blockquote&gt;
&lt;p&gt;👋 Hi!&lt;br &#x2F;&gt;
It&#x27;s Luca here, the author of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.zero2prod.com&#x2F;&quot;&gt;&quot;Zero to production in Rust&quot;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This is a progress report about &lt;code&gt;pavex&lt;&#x2F;code&gt;, a new Rust web framework that I have been working on. It is currently
in the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;early stages of development&lt;&#x2F;a&gt;, working towards its first alpha release.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;announcement post&lt;&#x2F;a&gt; to learn more about the vision!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h1&gt;
&lt;p&gt;It has been almost two months since &lt;a href=&quot;&#x2F;posts&#x2F;a-taste-of-pavex-rust-web-framework&#x2F;&quot;&gt;I announced&lt;&#x2F;a&gt; &lt;code&gt;pavex&lt;&#x2F;code&gt; on this blog.&lt;br &#x2F;&gt;
The community reaction has been strong, but definitely highly polarised (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=34195291&quot;&gt;HackerNews&lt;&#x2F;a&gt;,
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;zublfw&#x2F;a_taste_of_pavex_an_upcoming_rust_web_framework&#x2F;&quot;&gt;r&#x2F;rust&lt;&#x2F;a&gt;).
The vision resonated with some people, while others just can&#x27;t stand it.&lt;&#x2F;p&gt;
&lt;p&gt;I am &lt;em&gt;relieved&lt;&#x2F;em&gt;.&lt;br &#x2F;&gt;
I have spent months on working on this project, and I had started to wonder—is this a good idea?&lt;br &#x2F;&gt;
That was the whole point of the announcement: to poke the community and see if there is an appetite for this kind of framework.
I now &lt;em&gt;know&lt;&#x2F;em&gt; that is a niche out there that is willing to take a bet on something that looks &lt;em&gt;very&lt;&#x2F;em&gt; different from what
the current generation of Rust web frameworks has to offer.&lt;&#x2F;p&gt;
&lt;p&gt;Motivation is sorted, now it&#x27;s just a matter of building it!&lt;&#x2F;p&gt;
&lt;p&gt;There is a &lt;strong&gt;lot&lt;&#x2F;strong&gt; to be done ahead of the first alpha release. Going forward, I&#x27;ll be publishing &lt;strong&gt;monthly progress reports&lt;&#x2F;strong&gt;.
It&#x27;s a strategy to hold myself accountable and keep the community in the loop.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; is developed in the open. You can follow the project on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;
as well!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This is the first installment, looking back at the development work done in January and the first half of February 2023.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#overview&quot;&gt;Overview&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#table-of-contents&quot;&gt;Table of Contents&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#intoresponse-design&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt; design&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#error-handling&quot;&gt;Error handling&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#support-for-more-types&quot;&gt;Support for more types&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#avoid-bailing-out-on-the-first-error&quot;&gt;Avoid bailing out on the first error&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#better-foundations&quot;&gt;Better foundations&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;pavex-progress-report-01&#x2F;#what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;intoresponse-design&quot;&gt;&lt;code&gt;IntoResponse&lt;&#x2F;code&gt; design&lt;&#x2F;h2&gt;
&lt;p&gt;We finalised the design of the &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; trait:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;IntoResponse {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; Convert `self` into an HTTP response.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;into_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Response;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; trait is a quality of life improvement: it allows you to return arbitrary types from request
handlers or error handlers, as long as they can be converted into an HTTP response.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; This is a valid error handler in `pavex`, as long as `Home` implements `IntoResponse`.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_home&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request&amp;lt;Body&amp;gt;) -&amp;gt; Home { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The trait definition itself should look pretty familiar—it&#x27;s the same you&#x27;ll find in &lt;code&gt;axum&lt;&#x2F;code&gt; and, with minor variations,
in all other Rust web frameworks.&lt;&#x2F;p&gt;
&lt;p&gt;There is a twist though: &lt;code&gt;pavex&lt;&#x2F;code&gt; does &lt;strong&gt;not&lt;&#x2F;strong&gt; implement &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; for &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;&#x2F;code&gt; where &lt;code&gt;T: IntoResponse&lt;&#x2F;code&gt; and
&lt;code&gt;E: IntoResponse&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
Implementing &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; for &lt;code&gt;Result&lt;&#x2F;code&gt; &lt;em&gt;looks&lt;&#x2F;em&gt; like an ergonomic win, but things don&#x27;t usually play out that way
in non-trivial projects—at least based on my personal experience building Rust applications.&lt;&#x2F;p&gt;
&lt;p&gt;Where should the logic to convert an error into a HTTP response live, if &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;&#x2F;code&gt; implements &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If it&#x27;s simple enough, an &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; implementation would usually be the way to go.&lt;&#x2F;li&gt;
&lt;li&gt;Often enough &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; is too limited to express your conversion logic: you need to perform some async
processing or refer to some data that lives in the application state, which you do not have access to. As a consequence,
you move the conversion logic back into your request handler.&lt;&#x2F;li&gt;
&lt;li&gt;Last but least, it is common to have specific requirements around the representation of errors on the wire. Requirements that
conflict with the implementation of &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; provided out of the box for the error types returned by the extractors
that you are importing from third-party libraries. To make sure you don&#x27;t accidentally break your API contract, you
either need to newtype all error (in order to customize the &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; implementation) or you need to implement
the conversion logic in your request handler (again).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In &lt;code&gt;pavex&lt;&#x2F;code&gt;, where possible, we lean towards providing &lt;strong&gt;one&lt;&#x2F;strong&gt; way to do things&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#zen-python&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;br &#x2F;&gt;
As a consequence, we do not implement &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; for &lt;code&gt;Result&lt;&#x2F;code&gt; and provide a single mechanism to work with fallible
request handlers and constructors: registering an error handler for their error type.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; This is a valid error handler in `pavex`, as long as `Home` implements `IntoResponse`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; **and** there is a registered error handler for `GetHomeError`. 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_home&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request&amp;lt;Body&amp;gt;) -&amp;gt; Result&amp;lt;Home, GetHomeError&amp;gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; All error handlers are expected to take, as input, a reference to the error they want to handle.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;home_error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;HomeError) -&amp;gt; Response { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; AppBlueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The error handler is associated with the request handler here.
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::get_home), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;home&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_handler&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::home_error));
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This buys us some flexibility—you can, for example, choose to provide different error handlers for the same error type,
depending on the context, without having to create unnecessary new-type wrappers. Some conveniences will be provided
in order to register a single error handler for all occurrences of the same error type, but that&#x27;s a story for another
progress report.&lt;&#x2F;p&gt;
&lt;p&gt;Speaking of error handling...&lt;&#x2F;p&gt;
&lt;h2 id=&quot;error-handling&quot;&gt;Error handling&lt;&#x2F;h2&gt;
&lt;p&gt;The error handling story was barely sketched out in December. The implementation had a very narrow happy path.&lt;br &#x2F;&gt;
We are now in a much better place. You can:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Associate an error handler with a fallible request handler.&lt;&#x2F;li&gt;
&lt;li&gt;Associate an error handler with a fallible constructor, no matter the &lt;code&gt;Lifecycle&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Associate an error handler with both synchronous and asynchronous request handlers and constructors.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; A fallible constructor for `PathBuf`.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;extract_path&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;: Request&amp;lt;Body&amp;gt;) -&amp;gt; Result&amp;lt;PathBuf, ExtractPathError&amp;gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; All error handlers support dependency injection, just like constructors and request handlers!
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle_extract_path_error&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;ExtractPathError, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;logger&lt;&#x2F;span&gt;&lt;span&gt;: Logger) -&amp;gt; Response { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; AppBlueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::extract_path), Lifecycle::RequestScoped)
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The error handler is associated with the constructor here.
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;error_handler&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::handle_extract_path_error));
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We have added better guard rails as well. &lt;code&gt;pavex&lt;&#x2F;code&gt; will now validate that the error handler is compatible with the
request handler&#x2F;constructor you tried to associate it with.&lt;br &#x2F;&gt;
Code generation will fail if you register an error handler for an infallible constructor or if the error type does not line up.
This is what the error would look like in the first case:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-ERROR: 
&lt;&#x2F;span&gt;&lt;span&gt;  × You registered an error handler for a constructor that does not return a `Result`.
&lt;&#x2F;span&gt;&lt;span&gt;    ╭─[src&#x2F;lib.rs:22:1]
&lt;&#x2F;span&gt;&lt;span&gt; 22 │     bp.constructor(f!(crate::infallible_constructor), Lifecycle::RequestScoped)
&lt;&#x2F;span&gt;&lt;span&gt; 23 │         .error_handler(f!(crate::error_handler));
&lt;&#x2F;span&gt;&lt;span&gt;    ·                        ────────────┬───────────
&lt;&#x2F;span&gt;&lt;span&gt;    ·                                    ╰── The unnecessary error handler was registered here
&lt;&#x2F;span&gt;&lt;span&gt;    ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  help: Remove the error handler, it is not needed. The constructor is infallible!
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;support-for-more-types&quot;&gt;Support for more types&lt;&#x2F;h2&gt;
&lt;p&gt;The compile-time reflection engine sits at the core of &lt;code&gt;pavex&lt;&#x2F;code&gt;. It&#x27;s the mechanism that allows us to understand
the graph of dependencies between all the components of your application.&lt;&#x2F;p&gt;
&lt;p&gt;In December, we only had support for two families of types:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Qualified paths (e.g. &lt;code&gt;my_crate::my_module::MyType&lt;&#x2F;code&gt;), where &lt;code&gt;MyType&lt;&#x2F;code&gt; is either a struct or an enum;&lt;&#x2F;li&gt;
&lt;li&gt;Shared references to the above, as long as they had no lifetime qualifiers, e.g. &lt;code&gt;&amp;amp;MyType&lt;&#x2F;code&gt; but not &lt;code&gt;&amp;amp;&#x27;static MyType&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; would politely bail out when it encountered a type outside of its comfort zone:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;-Error: 
&lt;&#x2F;span&gt;&lt;span&gt;  × I do not know how to handle the type returned by `app::c`.
&lt;&#x2F;span&gt;&lt;span&gt;    ╭─[src&#x2F;lib.rs:8:1]
&lt;&#x2F;span&gt;&lt;span&gt;  8 │     let mut bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;  9 │     bp.route(f!(crate::c), &amp;quot;&#x2F;home&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    ·              ──────┬─────
&lt;&#x2F;span&gt;&lt;span&gt;    ·                    ╰── The request handler was registered here
&lt;&#x2F;span&gt;&lt;span&gt;    ╰────
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;   ╭─[src&#x2F;lib.rs:2:1]
&lt;&#x2F;span&gt;&lt;span&gt; 2 │ 
&lt;&#x2F;span&gt;&lt;span&gt; 3 │ pub fn c() -&amp;gt; (usize, usize) {
&lt;&#x2F;span&gt;&lt;span&gt;   ·               ───────┬──────
&lt;&#x2F;span&gt;&lt;span&gt;   ·                      ╰── The output type that I cannot handle
&lt;&#x2F;span&gt;&lt;span&gt;   ╰────
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The work on error handling demanded a much broader variety of types: at the very least, we should be able to handle all
the types that implement
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;868cdce9e89cf78edcb0dc40ee2c8d9c856fbd7c&#x2F;libs&#x2F;pavex_runtime&#x2F;src&#x2F;response&#x2F;into_response.rs#L64&quot;&gt;our &lt;code&gt;IntoResponse&lt;&#x2F;code&gt; trait&lt;&#x2F;a&gt;—e.g.
&lt;code&gt;&amp;amp;&#x27;static str&lt;&#x2F;code&gt;, &lt;code&gt;Cow&amp;lt;&#x27;static, u8&amp;gt;&lt;&#x2F;code&gt;, &lt;code&gt;&amp;amp;[u8]&lt;&#x2F;code&gt;, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Now we do!&lt;br &#x2F;&gt;
We have added support for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;All primitive types (e.g. &lt;code&gt;i32&lt;&#x2F;code&gt;, &lt;code&gt;bool&lt;&#x2F;code&gt;, &lt;code&gt;char&lt;&#x2F;code&gt;, &lt;code&gt;str&lt;&#x2F;code&gt;, &lt;code&gt;u8&lt;&#x2F;code&gt;, etc.);&lt;&#x2F;li&gt;
&lt;li&gt;Static references (e.g. &lt;code&gt;&amp;amp;&#x27;static str&lt;&#x2F;code&gt;);&lt;&#x2F;li&gt;
&lt;li&gt;Slices (e.g. &lt;code&gt;&amp;amp;[u8]&lt;&#x2F;code&gt;);&lt;&#x2F;li&gt;
&lt;li&gt;Tuples (e.g. &lt;code&gt;(i32, MyType)&lt;&#x2F;code&gt;);&lt;&#x2F;li&gt;
&lt;li&gt;Generic lifetime parameters, as long as they are set to &lt;code&gt;&#x27;static&lt;&#x2F;code&gt; (e.g. &lt;code&gt;Cow&amp;lt;&amp;amp;&#x27;static, str&amp;gt;&lt;&#x2F;code&gt;);&lt;&#x2F;li&gt;
&lt;li&gt;Type aliases (e.g. &lt;code&gt;type MyInteger = usize&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As a result, we can now handle &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;blob&#x2F;main&#x2F;libs&#x2F;pavex_cli&#x2F;tests&#x2F;ui_tests&#x2F;common_response_types_are_supported&#x2F;lib.rs&quot;&gt;all the types that implement &lt;code&gt;IntoResponse&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `pavex` does not choke on any of these types and will generate the correct code!
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; AppBlueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::response), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;response&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::static_str), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;static_str&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::string), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::vec_u8), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;vec_u8&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::cow_static_str), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;cow_static_str&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::bytes), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;bytes&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::bytes_mut), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;bytes_mut&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::empty), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;empty&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::status_code), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;status_code&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::parts), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;parts&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::full), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;full&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::static_u8_slice), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;static_u8_slice&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::cow_static_u8_slice), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;cow_static_u8_slice&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;avoid-bailing-out-on-the-first-error&quot;&gt;Avoid bailing out on the first error&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; used to bail out as soon as it encountered an error, which made for a frustrating experience when debugging
a failing build: fix one error, get another, fix that, get another, how many are still missing? Who knows.&lt;&#x2F;p&gt;
&lt;p&gt;We have changed this behaviour and now &lt;code&gt;pavex&lt;&#x2F;code&gt; will collect as many diagnostics as possible before exiting.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;better-foundations&quot;&gt;Better foundations&lt;&#x2F;h2&gt;
&lt;p&gt;The internals of &lt;code&gt;pavex&lt;&#x2F;code&gt; have gone through a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;commit&#x2F;4b378ea7ee881876af682f6d0b903593f300e1ab&quot;&gt;major overhaul&lt;&#x2F;a&gt;—a
full rewrite, if you will.
We have broken down the monolithic compiler implementation into a set of smaller analysis passes, each responsible
for a specific task and for keeping track of a certain type of component.&lt;br &#x2F;&gt;
This has allowed us to simplify the thornier parts of the codebase (e.g. the creation of the dependency graph for
each request handler) as well as reducing the boilerplate required to emit good error diagnostics—one of our driving
goals and features.&lt;&#x2F;p&gt;
&lt;p&gt;This would have been impossible to achieve if we had not invested early on in a
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&#x2F;tree&#x2F;main&#x2F;libs&#x2F;pavex_cli&#x2F;tests&#x2F;ui_tests&quot;&gt;solid suite of black-box end-to-end tests&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
There are almost no tests depending on the internal APIs of the compiler, everything goes through the (very narrow)
API of the CLI: you provide the Rust code of a &lt;code&gt;pavex&lt;&#x2F;code&gt; blueprint as input and make assertion against the generated code
(or the compiler errors you expect to run into). This decoupling empowers us to refactor aggressively and evolve
the internal architecture to meet our needs as the project grows in scope and complexity.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;what-s-next&quot;&gt;What&#x27;s next?&lt;&#x2F;h1&gt;
&lt;p&gt;In March we will focus on the request router.&lt;br &#x2F;&gt;
It is currently &lt;em&gt;extremely&lt;&#x2F;em&gt; basic—it does not even allow you to specify the HTTP method!&lt;br &#x2F;&gt;
We will be adding support for gating request handlers based on the HTTP method, as well as the ability to specify (and
extract) templated path segments (e.g. &lt;code&gt;id&lt;&#x2F;code&gt; in &lt;code&gt;&#x2F;users&#x2F;{id}&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;This will require some internal changes to the compiler logic—we currently assume that the set of injectables
types is the same for all the request handlers, but this will no longer be the case. This work will be the foundation
for allowing more advanced composition when laying down your application blueprints (e.g. nested routers, sub-blueprints, etc.).&lt;&#x2F;p&gt;
&lt;p&gt;See you next month!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Subscribe to the newsletter&lt;&#x2F;a&gt; if you don&#x27;t want to miss the next update!&lt;br &#x2F;&gt;
You can also follow the development of &lt;code&gt;pavex&lt;&#x2F;code&gt; on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;zen-python&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;&quot;There should be one—and preferably only one—obvious way to do it&quot;, one of
the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.python.org&#x2F;dev&#x2F;peps&#x2F;pep-0020&#x2F;&quot;&gt;Zen of Python&lt;&#x2F;a&gt; maxims that I happen to agree with.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2023-02-14T08:08:10.47Z&quot;&gt;February 2023&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;a-taste-of-pavex-rust-web-framework&amp;#x2F;&quot;&gt;Pavex announcement&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-02&amp;#x2F;&quot;&gt;Pavex report #2&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>A taste of pavex, an upcoming Rust web framework</title>
          <pubDate>Sat, 24 Dec 2022 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/a-taste-of-pavex-rust-web-framework/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/a-taste-of-pavex-rust-web-framework/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/a-taste-of-pavex-rust-web-framework/">&lt;h1 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h1&gt;
&lt;p&gt;Earlier this year, I started working on a new web framework for Rust: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The goal is simple: &lt;strong&gt;great ergonomics&lt;&#x2F;strong&gt; and &lt;strong&gt;high performance&lt;&#x2F;strong&gt; - no sacrifices.&lt;br &#x2F;&gt;
As easy to use as &lt;code&gt;tide&lt;&#x2F;code&gt;, Rails or ASP.NET Core.&lt;br &#x2F;&gt;
As fast as a handwritten solution built directly on top of raw &lt;code&gt;hyper&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been working on it for a few months now, enough to prove feasibility. It is not yet ready for user testing,
but the design has solidified enough to start talking about it.&lt;br &#x2F;&gt;
This post is an opportunity to open my workshop and share the vision: I want to understand if it
resonates with the broader Rust web community.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-state-of-rust-for-the-web&quot;&gt;The state of Rust for the web&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;actix-web&lt;&#x2F;code&gt;, &lt;code&gt;rocket&lt;&#x2F;code&gt;, &lt;code&gt;axum&lt;&#x2F;code&gt;, &lt;code&gt;tide&lt;&#x2F;code&gt;, &lt;code&gt;warp&lt;&#x2F;code&gt; - we have plenty of web frameworks in the Rust ecosystem, even
limiting the list to the most popular ones. Some would say we have &lt;strong&gt;too&lt;&#x2F;strong&gt; many - and here I am, working on making that
list even longer.&lt;&#x2F;p&gt;
&lt;p&gt;Why &lt;code&gt;pavex&lt;&#x2F;code&gt;? Why would you go and build yet another web framework?&lt;&#x2F;p&gt;
&lt;p&gt;To broaden the design space!&lt;br &#x2F;&gt;
I believe there is an under-explored opportunity to significantly &lt;strong&gt;improve the developer experience&lt;&#x2F;strong&gt; of Rust web
developers &lt;strong&gt;by raising the level of abstraction of their tools&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The current generation of Rust web frameworks is trying to walk a tight rope.&lt;br &#x2F;&gt;
On one side, they strive to provide ergonomic APIs, lowering the bar for more and more people to get started building
APIs in Rust.&lt;br &#x2F;&gt;
On the other side, they want to provide high-performance and (wherever possible) misuse-resistant interfaces with
compile-time guarantees of correctness.&lt;&#x2F;p&gt;
&lt;p&gt;There is tension between those two objectives.&lt;br &#x2F;&gt;
High-performance and compile-safety drives frameworks to lean heavily on the expressiveness of Rust&#x27;s type systems,
trying to encode invariants for compile-time verification as well as limiting the overhead of the framework itself.&lt;br &#x2F;&gt;
This all works just fine on the happy path, but it can lead to obscure compiler errors on the unhappy path - often
too obscure for beginners trying to make sense of what is happening.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s look at &lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s &quot;Hello world&quot; example as a case study:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;axum::{response::Html, routing::get, Router};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::net::SocketAddr;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; app = Router::new().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(handler));
&lt;&#x2F;span&gt;&lt;span&gt;    axum::Server::bind(&amp;amp;SocketAddr::from(([&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;], &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3000&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;serve&lt;&#x2F;span&gt;&lt;span&gt;(app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into_make_service&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Html&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    Html(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;h1&amp;gt;Hello, World!&amp;lt;&#x2F;h1&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;axum&lt;&#x2F;code&gt; requires all handler functions to be asynchronous. What happens if you forget?&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; No longer `async`!
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Html&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The compiler greets us with this error message:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;error[E0277]: the trait bound `fn() -&amp;gt; Html&amp;lt;&amp;amp;&amp;#39;static str&amp;gt; {handler}: Handler&amp;lt;_, _, _&amp;gt;` is not satisfied
&lt;&#x2F;span&gt;&lt;span&gt;   --&amp;gt; hello-world&#x2F;src&#x2F;main.rs:12:44
&lt;&#x2F;span&gt;&lt;span&gt;    |
&lt;&#x2F;span&gt;&lt;span&gt;12  |     let app = Router::new().route(&amp;quot;&#x2F;&amp;quot;, get(handler));
&lt;&#x2F;span&gt;&lt;span&gt;    |                                        --- ^^^^^^^ 
&lt;&#x2F;span&gt;&lt;span&gt;                                             |   the trait `Handler&amp;lt;_, _, _&amp;gt;` is not implemented 
&lt;&#x2F;span&gt;&lt;span&gt;                                             |   for `fn() -&amp;gt; Html&amp;lt;&amp;amp;&amp;#39;static str&amp;gt; {handler}`
&lt;&#x2F;span&gt;&lt;span&gt;    |                                        |
&lt;&#x2F;span&gt;&lt;span&gt;    |                                        required by a bound introduced by this call
&lt;&#x2F;span&gt;&lt;span&gt;    |
&lt;&#x2F;span&gt;&lt;span&gt;    = help: the trait `Handler&amp;lt;T, S, B&amp;gt;` is implemented for `Layered&amp;lt;L, H, T, S, B&amp;gt;`
&lt;&#x2F;span&gt;&lt;span&gt;note: required by a bound in `axum::routing::get`
&lt;&#x2F;span&gt;&lt;span&gt;   --&amp;gt; &#x2F;Users&#x2F;luca&#x2F;code&#x2F;axum&#x2F;axum&#x2F;src&#x2F;routing&#x2F;method_routing.rs:400:1
&lt;&#x2F;span&gt;&lt;span&gt;    |
&lt;&#x2F;span&gt;&lt;span&gt;400 | top_level_handler_fn!(get, GET);
&lt;&#x2F;span&gt;&lt;span&gt;    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
&lt;&#x2F;span&gt;&lt;span&gt;    = note: this error originates in the macro `top_level_handler_fn`
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Good luck figuring that out!&lt;br &#x2F;&gt;
Especially if you are at the beginning of your journey with Rust.&lt;&#x2F;p&gt;
&lt;p&gt;Is the situation helpless? Are we forced to choose between performance, compile-time safety and ergonomics?&lt;&#x2F;p&gt;
&lt;p&gt;No, we are not.&lt;br &#x2F;&gt;
There are ongoing efforts inside the Rust project to empower crate authors to &quot;suggest&quot; to the compiler appropriate
error messages in specific situations (check out &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;5786&quot;&gt;this PR&lt;&#x2F;a&gt; in &lt;code&gt;bevy&lt;&#x2F;code&gt;!),
thus improving the quality of diagnostics.&lt;&#x2F;p&gt;
&lt;p&gt;At the same time, crate authors are trying to step in with the tools currently available in Rust&#x27;s latest stable
release: metaprogramming.&lt;br &#x2F;&gt;
&lt;code&gt;axum&lt;&#x2F;code&gt; provides a &lt;code&gt;#[debug_handler]&lt;&#x2F;code&gt; procedural macro that can be used to annotate request handlers. It has no effect on
runtime behaviour, but it allows &lt;code&gt;axum&lt;&#x2F;code&gt; to preempt the compiler and emit &lt;em&gt;its own compiler errors&lt;&#x2F;em&gt; when it detects
certain patterns of incorrect behaviour.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s use the &quot;Hello world&quot; example again to try it out:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Now annotated with #[debug_handler], same sync signature otherwise
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;axum&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;debug_handler&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handler&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Html&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The error message is now &lt;strong&gt;much&lt;&#x2F;strong&gt; better:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;error: handlers must be async functions
&lt;&#x2F;span&gt;&lt;span&gt;  --&amp;gt; main.rs:xx:1
&lt;&#x2F;span&gt;&lt;span&gt;   |
&lt;&#x2F;span&gt;&lt;span&gt;xx | fn handler() -&amp;gt; &amp;amp;&amp;#39;static str {
&lt;&#x2F;span&gt;&lt;span&gt;   | ^^
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is &lt;strong&gt;amazing&lt;&#x2F;strong&gt;, because it is speaking &lt;strong&gt;at the right level of abstraction&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
It is talking about handlers, a concept that we understand as API developers. A concept that we had just tried to use.&lt;br &#x2F;&gt;
No type noise, no need to look at the open guts of &lt;code&gt;axum&lt;&#x2F;code&gt;&#x27;s inner abstractions.&lt;&#x2F;p&gt;
&lt;p&gt;This is where the idea for &lt;code&gt;pavex&lt;&#x2F;code&gt; comes from.&lt;br &#x2F;&gt;
What if we took this metaprogramming approach to the next level?&lt;br &#x2F;&gt;
Let&#x27;s get rid of most of the user-facing complexity - heavily generic APIs, intricate trait bounds, nested type chains.
We will use straight-forward Rust to specify what your application should do.&lt;br &#x2F;&gt;
We will then feed our app specification to &lt;code&gt;pavex_cli&lt;&#x2F;code&gt;, a transpiler designed &lt;em&gt;specifically&lt;&#x2F;em&gt; for web applications.&lt;br &#x2F;&gt;
It will generate the source code for our web server, using (once again) straight-forward Rust. If something goes wrong,
it will return &lt;strong&gt;meaningful errors that speak the language of web applications&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;You might be wondering - is it even feasible? You&#x27;d be right to doubt!&lt;br &#x2F;&gt;
There are significant technical challenges in building a tool that lives up to the vision laid out above.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;an-overview-of-pavex&quot;&gt;An overview of &lt;code&gt;pavex&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Before diving into the technical details, let&#x27;s get a feeling for the type of API that &lt;code&gt;pavex&lt;&#x2F;code&gt; will expose.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;pavex_builder::{f, AppBlueprint, Lifecycle};
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; Return the blueprint for our application.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; AppBlueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::http_client), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::extract_path), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::logger), Lifecycle::Transient);
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; vault = bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;vault&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    vault.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::stream_file));
&lt;&#x2F;span&gt;&lt;span&gt;    
&lt;&#x2F;span&gt;&lt;span&gt;    bp
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What is going on here?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;appblueprint-the-compile-time-representation&quot;&gt;&lt;code&gt;AppBlueprint&lt;&#x2F;code&gt;, the compile-time representation&lt;&#x2F;h3&gt;
&lt;p&gt;We have an HTTP router, which should look familiar:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; vault = bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;route&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;vault&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Use `stream_file` to handle `GET` requests to `&#x2F;vault`.
&lt;&#x2F;span&gt;&lt;span&gt;vault.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::stream_file));
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We are also registering &lt;strong&gt;constructors&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::http_client), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::extract_path), Lifecycle::RequestScoped);
&lt;&#x2F;span&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::logger), Lifecycle::Transient);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This pattern is not very common in the Rust ecosystem, therefore it might not be as familiar: &lt;code&gt;pavex&lt;&#x2F;code&gt; performs
&lt;strong&gt;(compile-time) dependency injection&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Instead of providing the framework with an instance of the types you want to use, you provide it with a function that
builds those types. The framework will then call your function to build the instances it needs and pass them to your
handlers and middlewares, whenever they are needed.&lt;&#x2F;p&gt;
&lt;p&gt;Our example has a single request handler, &lt;code&gt;stream_file&lt;&#x2F;code&gt;. This is its signature:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;stream_file&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: Request&amp;lt;Body&amp;gt;, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;filepath&lt;&#x2F;span&gt;&lt;span&gt;: PathBuf, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;logger&lt;&#x2F;span&gt;&lt;span&gt;: Logger, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http_client&lt;&#x2F;span&gt;&lt;span&gt;: HttpClient
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Response { 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; 
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Whenever a &lt;code&gt;GET &#x2F;vault&lt;&#x2F;code&gt; request is received, the framework must invoke &lt;code&gt;stream_file&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
&lt;code&gt;pavex&lt;&#x2F;code&gt; looks at &lt;code&gt;stream_file&lt;&#x2F;code&gt;&#x27;s signature (we&#x27;ll see later how) and figures out that it must provide the handler with an
instance of &lt;code&gt;PathBuf&lt;&#x2F;code&gt;, &lt;code&gt;Request&amp;lt;Body&amp;gt;&lt;&#x2F;code&gt;, &lt;code&gt;Logger&lt;&#x2F;code&gt; and &lt;code&gt;HttpClient&lt;&#x2F;code&gt; - the &lt;strong&gt;dependencies&lt;&#x2F;strong&gt; of our request handler.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Request&amp;lt;Body&amp;gt;&lt;&#x2F;code&gt; is the incoming HTTP request, it will be injected by &lt;code&gt;pavex&lt;&#x2F;code&gt;. For the other three dependencies, &lt;code&gt;pavex&lt;&#x2F;code&gt;
looks for their constructors:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;extract_path&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;request&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;Request&amp;lt;Body&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;logger&lt;&#x2F;span&gt;&lt;span&gt;: Logger) -&amp;gt; PathBuf { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;logger&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Logger { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;http_client&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;configuration&lt;&#x2F;span&gt;&lt;span&gt;: Config) -&amp;gt; HttpClient { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Constructors themselves can have dependencies, which are resolved in the same way.&lt;br &#x2F;&gt;
&lt;code&gt;pavex&lt;&#x2F;code&gt;, though, needs to know more than just the constructor for a type: it also needs to know how &lt;strong&gt;often&lt;&#x2F;strong&gt; it
should be invoked.
This is what we call &lt;strong&gt;lifecycle&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lifecycle&lt;&#x2F;th&gt;&lt;th&gt;Description&lt;&#x2F;th&gt;&lt;th&gt;Example&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Singleton&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;The instance is created once, when the application starts. It is then reused for all incoming requests. Singletons are your application state.&lt;&#x2F;td&gt;&lt;td&gt;Database connection pools, HTTP connection pools, configuration.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;RequestScoped&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;The instance is created once per request. It is then reused for all processing within the same request.&lt;&#x2F;td&gt;&lt;td&gt;Path parameters, auth information, parsed request body.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Transient&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;The instance is created every time it is needed.&lt;&#x2F;td&gt;&lt;td&gt;Database connections, logger instances.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Routes, constructors and their lifecycles are combined into a single &lt;code&gt;AppBlueprint&lt;&#x2F;code&gt; instance: this is a full
specification of your application.&lt;br &#x2F;&gt;
This representation only exists at &lt;strong&gt;compile-time&lt;&#x2F;strong&gt;. It is never used at runtime.&lt;br &#x2F;&gt;
&lt;code&gt;pavex_cli&lt;&#x2F;code&gt; &lt;strong&gt;transpiles&lt;&#x2F;strong&gt; this blueprint into a runtime crate - a runnable web server.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The blueprint we defined above.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; bp = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    pavex_cli::generate(bp)
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The folder where the generated crate will live.
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;output_path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;()?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;the-runtime-code&quot;&gt;The runtime code&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;the-entrypoint&quot;&gt;The entrypoint&lt;&#x2F;h4&gt;
&lt;p&gt;The main entrypoint of the generated crate is the &lt;code&gt;run&lt;&#x2F;code&gt; function:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;server_builder&lt;&#x2F;span&gt;&lt;span&gt;: hyper::server::Builder&amp;lt;AddrIncoming&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;application_state&lt;&#x2F;span&gt;&lt;span&gt;: ApplicationState,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), pavex_runtime::Error&amp;gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;run&lt;&#x2F;code&gt; launches the web server so that you can start accepting requests. It takes as input the HTTP server configuration
(&lt;code&gt;hyper::server::Builder&lt;&#x2F;code&gt;) and an instance of the application state.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;application-state&quot;&gt;Application state&lt;&#x2F;h4&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;ApplicationState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;s0&lt;&#x2F;span&gt;&lt;span&gt;: app::HttpClient,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;ApplicationState {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v0&lt;&#x2F;span&gt;&lt;span&gt;: app::Config) -&amp;gt; ApplicationState {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; v1 = app::http_client(v0);
&lt;&#x2F;span&gt;&lt;span&gt;        ApplicationState { s0: v1 }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can build an instance of &lt;code&gt;ApplicationState&lt;&#x2F;code&gt; by calling &lt;code&gt;ApplicationState::new&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
The only singleton needed at runtime is &lt;code&gt;HttpClient&lt;&#x2F;code&gt;, but that&#x27;s not what &lt;code&gt;ApplicationState::new&lt;&#x2F;code&gt; asks for as input:
it wants an &lt;code&gt;app::Config&lt;&#x2F;code&gt; instance. Why?
It&#x27;s due to the constructor we registered for &lt;code&gt;HttpClient&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;http_client&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;configuration&lt;&#x2F;span&gt;&lt;span&gt;: Config) -&amp;gt; HttpClient { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F; &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It wants a &lt;code&gt;Config&lt;&#x2F;code&gt; instance as input, but we didn&#x27;t register any constructor for &lt;code&gt;Config&lt;&#x2F;code&gt; in our blueprint. Therefore
&lt;code&gt;pavex&lt;&#x2F;code&gt; generates an &lt;code&gt;ApplicationState::new&lt;&#x2F;code&gt; function that takes &lt;code&gt;Config&lt;&#x2F;code&gt; as input and builds the rest of the
state for us.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;request-handlers&quot;&gt;Request handlers&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;code&gt;pavex&lt;&#x2F;code&gt; generates a function for each request handler in our blueprint.&lt;br &#x2F;&gt;
We can see the signature of the &lt;code&gt;GET &#x2F;vault&lt;&#x2F;code&gt; handler:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_vault&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v0&lt;&#x2F;span&gt;&lt;span&gt;: app::HttpClient, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v1&lt;&#x2F;span&gt;&lt;span&gt;: Request&amp;lt;Body&amp;gt;) -&amp;gt; Response {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; v3 = app::extract_path(&amp;amp;v1, app::logger()).await;
&lt;&#x2F;span&gt;&lt;span&gt;    app::stream_file(v1, v3, app::logger(), v0).await
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The handler takes as input the &lt;code&gt;HttpClient&lt;&#x2F;code&gt; singleton (out of &lt;code&gt;ApplicationState&lt;&#x2F;code&gt;) and the incoming HTTP request.&lt;br &#x2F;&gt;
We can see how &lt;code&gt;pavex&lt;&#x2F;code&gt; walked the &lt;strong&gt;dependency graph&lt;&#x2F;strong&gt; of our request handler, honoring the lifecycle of each type:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HttpClient&lt;&#x2F;code&gt; is a singleton, therefore it comes from the &lt;code&gt;ApplicationState&lt;&#x2F;code&gt; and it is passed as input to the handler;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Request&amp;lt;Body&amp;gt;&lt;&#x2F;code&gt; is request-scoped, the same instance is passed to both &lt;code&gt;extract_path&lt;&#x2F;code&gt; (as a reference) and &lt;code&gt;stream_file&lt;&#x2F;code&gt; (by value);&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Logger&lt;&#x2F;code&gt; is transient, a new instance is created every time it is needed as input.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;a-zero-cost-abstraction&quot;&gt;A zero-cost abstraction&lt;&#x2F;h3&gt;
&lt;p&gt;The generated code contains no indirection - no runtime reflection (as it&#x27;s often the case for dependency injection
frameworks other languages), no dynamic dispatch, no type-maps.&lt;br &#x2F;&gt;
It&#x27;s just a bunch of functions that call each other, passing around the instances they need.&lt;br &#x2F;&gt;
It&#x27;s a &lt;strong&gt;zero-cost abstraction&lt;&#x2F;strong&gt;: you would get the same performance if you had written the code by hand.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the whole value proposition of &lt;code&gt;pavex&lt;&#x2F;code&gt;: ergonomics &lt;strong&gt;and&lt;&#x2F;strong&gt; performance.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-unhappy-path&quot;&gt;The unhappy path&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;ve mostly shown you the happy path - you throw a valid &lt;code&gt;AppBlueprint&lt;&#x2F;code&gt; at &lt;code&gt;pavex&lt;&#x2F;code&gt; and you get back a runnable web server.&lt;br &#x2F;&gt;
But what happens if you make a mistake?&lt;&#x2F;p&gt;
&lt;p&gt;This is, perhaps surprisingly, the aspect of &lt;code&gt;pavex&lt;&#x2F;code&gt; that I&#x27;m most excited about: it&#x27;s a compiler operating at a higher
level of abstraction than &lt;code&gt;rustc&lt;&#x2F;code&gt;, therefore we can provide &lt;strong&gt;better error messages&lt;&#x2F;strong&gt; for our target usecase.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s see what happens if try to register a singleton that is not &lt;code&gt;Send&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Definitely not `Send`, given that it contains an `Rc`.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;NonSendSingleton(Rc&amp;lt;()&amp;gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;blueprint&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; AppBlueprint {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt;    bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::NonSendSingleton::new), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;pavex_cli&lt;&#x2F;code&gt; returns an error when trying to generate the runtime code:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;Error: 
&lt;&#x2F;span&gt;&lt;span&gt;  × `app::NonSendSingleton` does not implement the `core::marker::Send` trait.
&lt;&#x2F;span&gt;&lt;span&gt;    ╭─[src&#x2F;lib.rs:24:1]
&lt;&#x2F;span&gt;&lt;span&gt; 24 │     let mut bp = AppBlueprint::new();
&lt;&#x2F;span&gt;&lt;span&gt; 25 │     bp.constructor(f!(crate::NonSendSingleton::new), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;span&gt;    ·                    ────────────────┬───────────────
&lt;&#x2F;span&gt;&lt;span&gt;    ·                                    ╰── The constructor was registered here
&lt;&#x2F;span&gt;&lt;span&gt; 26 │     
&lt;&#x2F;span&gt;&lt;span&gt;    ╰────
&lt;&#x2F;span&gt;&lt;span&gt;  help: All singletons must implement the `Send` trait.
&lt;&#x2F;span&gt;&lt;span&gt;        `pavex` runs on a multi-threaded HTTP server and singletons must be
&lt;&#x2F;span&gt;&lt;span&gt;        shared across all worker threads.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The error message, going back to the beginning of the post, is &lt;strong&gt;at the right level of abstraction&lt;&#x2F;strong&gt;. It speaks
of singletons and HTTP servers, the concepts that we are working with.&lt;br &#x2F;&gt;
It does not only say that &lt;code&gt;NonSendSingleton&lt;&#x2F;code&gt; does not implement &lt;code&gt;Send&lt;&#x2F;code&gt;, but &lt;strong&gt;it also explains why this is a problem&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;At the same time, there is more work to be done: the error message does not explain why &lt;code&gt;NonSendSingleton&lt;&#x2F;code&gt; does not
implement &lt;code&gt;Send&lt;&#x2F;code&gt;. I&#x27;m currently exploring how to leverage &lt;code&gt;rustc&lt;&#x2F;code&gt; to &lt;strong&gt;combine&lt;&#x2F;strong&gt; both sources of information for an
optimal experience.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-peek-under-the-hood&quot;&gt;A peek under the hood&lt;&#x2F;h2&gt;
&lt;p&gt;You might be wondering, at this point: how does &lt;code&gt;pavex&lt;&#x2F;code&gt; actually work?&lt;&#x2F;p&gt;
&lt;p&gt;Dependency-injection frameworks are usually implemented via runtime reflection: you can go to the language runtime
with a function pointer and introspect it - input types, output type, asyncness, etc.&lt;br &#x2F;&gt;
Rust has no reflection API.&lt;&#x2F;p&gt;
&lt;p&gt;Well, no &lt;strong&gt;runtime&lt;&#x2F;strong&gt; reflection API.&lt;br &#x2F;&gt;
We live in exciting times. Rust has started to grow a &lt;strong&gt;compile-time&lt;&#x2F;strong&gt; reflection API and &lt;code&gt;pavex&lt;&#x2F;code&gt; is built on top of it:
&lt;strong&gt;&lt;code&gt;rustdoc&lt;&#x2F;code&gt;&#x27;s JSON output&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
It&#x27;s exactly what it sounds like: a JSON version of the crate documentation that you can find on
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&quot;&gt;docs.rs&lt;&#x2F;a&gt;, currently only available on &lt;code&gt;nightly&lt;&#x2F;code&gt;.
You can generate it for a crate by running:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span&gt; +nightly rustdoc&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -p &lt;&#x2F;span&gt;&lt;span&gt;{crate_name}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; --lib&lt;&#x2F;span&gt;&lt;span&gt; -- -Zunstable-options -wjson
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You get as output a JSON file with a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;rustdoc-types&quot;&gt;structured representation&lt;&#x2F;a&gt;
of all the types in your crate.&lt;br &#x2F;&gt;
This is exactly what &lt;code&gt;pavex&lt;&#x2F;code&gt; does: for each registered route handler and constructor, it builds the documentation for
the crate it belongs to and extracts the relevant bits of information from &lt;code&gt;rustdoc&lt;&#x2F;code&gt;&#x27;s output.&lt;&#x2F;p&gt;
&lt;p&gt;You might have noticed the &lt;code&gt;f!&lt;&#x2F;code&gt; macro in the examples above:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;bp.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;constructor&lt;&#x2F;span&gt;&lt;span&gt;(f!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::http_client), Lifecycle::Singleton);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;f!(crate::http_client)&lt;&#x2F;code&gt; desugars to&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;pavex_builder::RawCallable {
&lt;&#x2F;span&gt;&lt;span&gt;    import_path: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;crate::http_client&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    callable: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;crate&lt;&#x2F;span&gt;&lt;span&gt;::http_client,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;which is further transformed by the &lt;code&gt;AppBlueprint&lt;&#x2F;code&gt; into a &lt;code&gt;pavex_builder::RawCallableIdentifiers&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;pavex_builder::RawCallableIdentifiers {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Name of the crate where `blueprint.constructor(..)` was called
&lt;&#x2F;span&gt;&lt;span&gt;    registered_at: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Stringified fully-qualified path to the function
&lt;&#x2F;span&gt;&lt;span&gt;    import_path: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;crate::http_client&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the information fed to &lt;code&gt;pavex&lt;&#x2F;code&gt; as input, via &lt;code&gt;AppBlueprint&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For each identifier, &lt;code&gt;pavex&lt;&#x2F;code&gt; looks at the &lt;code&gt;Cargo.lock&lt;&#x2F;code&gt; for the current workspace, finds the &lt;code&gt;registered_at&lt;&#x2F;code&gt; crate, determines its
dependencies and generates the documentation for the crate where the callable is defined (in the example above,
&lt;code&gt;app&lt;&#x2F;code&gt; itself, since &lt;code&gt;import_path&lt;&#x2F;code&gt; begins with &lt;code&gt;crate&lt;&#x2F;code&gt;).&lt;br &#x2F;&gt;
It then walks the JSON documentation to find the signature of &lt;code&gt;http_client&lt;&#x2F;code&gt; and repeats the process for all its
inputs and outputs.&lt;&#x2F;p&gt;
&lt;p&gt;All this information is assembled in a series of &lt;strong&gt;call graphs&lt;&#x2F;strong&gt;, one for each registered route handler and one for
the application state.&lt;&#x2F;p&gt;
&lt;figure style=&quot;text-align:center;&quot; &gt;
    &lt;img src=&quot;&#x2F;image&#x2F;a-taste-of-pavex&#x2F;get_vault_graph.svg&quot; alt=&quot;The call graph for `GET &#x2F;vault`&quot;&gt;
    &lt;figcaption style=&quot;margin-top:0.5em;&quot;&gt;&lt;i&gt;The call graph for `GET &#x2F;vault`, the route in our example.&lt;&#x2F;i&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The call graphs are then used to drive the code generation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-future&quot;&gt;The future&lt;&#x2F;h2&gt;
&lt;p&gt;I hope I showed you enough to be intrigued.&lt;&#x2F;p&gt;
&lt;p&gt;Back to reality though: &lt;code&gt;pavex&lt;&#x2F;code&gt; is still in its early days, not yet ready for user testing. I have intentionally
avoided publishing the crates on crates.io, the API is too experimental and it would be detrimental
to encourage its usage at this stage.&lt;&#x2F;p&gt;
&lt;p&gt;There is a lot to work on! Error handling, middleware(s), robust diagnostics, examples, project funding, etc.&lt;br &#x2F;&gt;
If all goes according to plan, I expect to have a first alpha version ready by July 2023.&lt;&#x2F;p&gt;
&lt;p&gt;In the meantime, you can follow the development on the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;pavex&quot;&gt;GitHub repository&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
It&#x27;s going to be a fun ride!&lt;&#x2F;p&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2022-12-24T08:08:10.47Z&quot;&gt;December 2022&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;pavex-progress-report-01&amp;#x2F;&quot;&gt;Pavex Progress Report #1&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
</description>
      </item>
      <item>
          <title>An In-Depth Introduction To Idempotency</title>
          <pubDate>Mon, 14 Mar 2022 08:08:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/idempotency/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/idempotency/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/idempotency/">&lt;blockquote&gt;
&lt;p&gt;This article is a sample from &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;&lt;strong&gt;Zero To Production In Rust&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, a hands-on introduction to backend development in Rust.&lt;br &#x2F;&gt;
You can get a copy of the book at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;zero2prod.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h1&gt;
&lt;p&gt;We kept the first iteration of our newsletter endpoint very simple: emails are immediately sent out to all subscribers via Postmark, one API call at a time.&lt;br &#x2F;&gt;
This is good enough if the audience is small - it breaks down, in a variety of ways, when dealing with hundreds of subscribers.&lt;&#x2F;p&gt;
&lt;p&gt;We want our application to be &lt;strong&gt;fault-tolerant&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Newsletter delivery should not be disrupted by transient failures like application crashes, Postmark API errors or network timeouts. To deliver a reliable service in the face of failure we will have to explore new concepts: idempotency, locking, queues and background jobs.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter-11&quot;&gt;Chapter 11&lt;&#x2F;h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#1-post-admin-newsletters-a-refresher&quot;&gt;1. &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; - A Refresher&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#2-our-goal&quot;&gt;2. Our Goal&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-failure-modes&quot;&gt;3. Failure Modes&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-1-invalid-inputs&quot;&gt;3.1. Invalid Inputs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-2-network-i-o&quot;&gt;3.2. Network I&#x2F;O&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-2-1-postgres&quot;&gt;3.2.1. Postgres&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-2-2-postmark-api-errors&quot;&gt;3.2.2. Postmark - API Errors&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-3-application-crashes&quot;&gt;3.3. Application Crashes&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#3-4-author-actions&quot;&gt;3.4. Author Actions&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#4-idempotency-an-introduction&quot;&gt;4. Idempotency: An Introduction&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#4-1-idempotency-in-action-payments&quot;&gt;4.1. Idempotency In Action: Payments&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#4-2-idempotency-keys&quot;&gt;4.2. Idempotency Keys&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#4-3-concurrent-requests&quot;&gt;4.3. Concurrent Requests&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#5-requirements-as-tests-1&quot;&gt;5. Requirements As Tests #1&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#6-implementation-strategies&quot;&gt;6. Implementation Strategies&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#6-1-stateful-idempotency-save-and-replay&quot;&gt;6.1. Stateful Idempotency: Save And Replay&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#6-2-stateless-idempotency-deterministic-key-generation&quot;&gt;6.2. Stateless Idempotency: Deterministic Key Generation&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#6-3-time-is-a-tricky-beast&quot;&gt;6.3. Time Is a Tricky Beast&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#6-4-making-a-choice&quot;&gt;6.4. Making A Choice&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#7-idempotency-store&quot;&gt;7. Idempotency Store&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#7-1-which-database-should-we-use&quot;&gt;7.1. Which Database Should We Use?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#7-2-schema&quot;&gt;7.2. Schema&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-save-and-replay&quot;&gt;8. Save And Replay&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-1-read-idempotency-key&quot;&gt;8.1. Read Idempotency Key&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-2-retrieve-saved-responses&quot;&gt;8.2. Retrieve Saved Responses&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-3-save-responses&quot;&gt;8.3. Save Responses&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-3-1-messagebody-and-http-streaming&quot;&gt;8.3.1. &lt;code&gt;MessageBody&lt;&#x2F;code&gt; and HTTP Streaming&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-3-2-array-of-composite-postgres-types&quot;&gt;8.3.2. Array Of Composite Postgres Types&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#8-3-3-plug-it-in&quot;&gt;8.3.3. Plug It In&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#9-concurrent-requests&quot;&gt;9. Concurrent Requests&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#9-1-requirements-as-tests-2&quot;&gt;9.1. Requirements As Tests #2&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#9-2-synchronization&quot;&gt;9.2. Synchronization&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#9-2-1-transaction-isolation-levels&quot;&gt;9.2.1. Transaction Isolation Levels&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-dealing-with-errors&quot;&gt;10. Dealing With Errors&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-1-distributed-transactions&quot;&gt;10.1. Distributed Transactions&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-2-backward-recovery&quot;&gt;10.2. Backward Recovery&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-3-forward-recovery&quot;&gt;10.3. Forward Recovery&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-asynchronous-processing&quot;&gt;10.4. Asynchronous Processing&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-1-newsletter-issues&quot;&gt;10.4.1. &lt;code&gt;newsletter_issues&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-2-issue-delivery-queue&quot;&gt;10.4.2. &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-3-post-admin-newsletters&quot;&gt;10.4.3. &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-4-email-processing&quot;&gt;10.4.4. Email Processing&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-5-worker-loop&quot;&gt;10.4.5. Worker loop&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-6-launching-background-workers&quot;&gt;10.4.6. Launching Background Workers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#10-4-7-updating-the-test-suite&quot;&gt;10.4.7. Updating The Test Suite&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;idempotency&#x2F;#11-epilogue&quot;&gt;11. Epilogue&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;1-post-admin-newsletters-a-refresher&quot;&gt;1. &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; - A Refresher&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s refresh our memory before jumping straight into the task: what does &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; look like?&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#take-home&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The endpoint is invoked when a logged-in newsletter author submits the HTML form served at &lt;code&gt;GET &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
We parse the form data out of the HTTP request body and, if nothing is amiss, kick-off the processing.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(serde::Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;FormData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span&gt;: web::Form&amp;lt;FormData&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: web::Data&amp;lt;PgPool&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email_client&lt;&#x2F;span&gt;&lt;span&gt;: web::Data&amp;lt;EmailClient&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We start by fetching all confirmed subscribers from our Postgres database.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; subscribers = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool).await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;ConfirmedSubscriber {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span&gt;: SubscriberEmail,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Vec&amp;lt;Result&amp;lt;ConfirmedSubscriber, anyhow::Error&amp;gt;&amp;gt;, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We iterate over the retrieved subscribers, sequentially.&lt;br &#x2F;&gt;
For each user, we try to send out an email with the new newsletter issue.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; subscribers = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool).await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; subscriber in subscribers {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; subscriber {
&lt;&#x2F;span&gt;&lt;span&gt;            Ok(subscriber) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                email_client
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send_email&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                    .await
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;with_context&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            Err(error) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                tracing::warn!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    FlashMessage::info(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;The newsletter issue has been published!&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;see_other&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&#x2F;newsletters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once all subscribers have been taken care of, we redirect the author back to the newsletter form - they will be shown a flash message confirming that the issue was published successfully.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;2-our-goal&quot;&gt;2. Our Goal&lt;&#x2F;h1&gt;
&lt;p&gt;We want to ensure &lt;strong&gt;best-effort delivery&lt;&#x2F;strong&gt;: we strive to deliver the new newsletter issue to all subscribers.&lt;br &#x2F;&gt;
We cannot &lt;em&gt;guarantee&lt;&#x2F;em&gt; that all emails will be delivered: some accounts might just have been deleted.&lt;&#x2F;p&gt;
&lt;p&gt;At the same time, we should try to minimize duplicates - i.e. a subscriber receiving the same issue multiple times. We cannot rule out duplicates entirely (we will later discuss why), but our implementation should minimize their frequency.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;3-failure-modes&quot;&gt;3. Failure Modes&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s have a look at the possible failure modes of our &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; endpoint.&lt;br &#x2F;&gt;
Can we still achieve best-effort delivery when something goes awry?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-1-invalid-inputs&quot;&gt;3.1. Invalid Inputs&lt;&#x2F;h2&gt;
&lt;p&gt;There might be issues with the incoming request: the body is malformed or the user has not authenticated.&lt;br &#x2F;&gt;
Both scenarios are already handled appropriately:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;web::Form&lt;&#x2F;code&gt; extractor returns a &lt;code&gt;400 Bad Request&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#form-bad-request&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; if the incoming form data is invalid;&lt;&#x2F;li&gt;
&lt;li&gt;unauthenticated users are redirected back to the login form.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;3-2-network-i-o&quot;&gt;3.2. Network I&#x2F;O&lt;&#x2F;h2&gt;
&lt;p&gt;Problems might arise when we interact with other machines over the network.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-2-1-postgres&quot;&gt;3.2.1. Postgres&lt;&#x2F;h3&gt;
&lt;p&gt;The database might misbehave when we try to retrieve the current list of subscribers. We do not have a lot of options apart from retrying. We can:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;retry in process, by adding some logic around the &lt;code&gt;get_confirmed_subscribers&lt;&#x2F;code&gt; call;&lt;&#x2F;li&gt;
&lt;li&gt;give up by returning an error to the user. The user can then decide if they want to retry or not.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The first option makes our application more resilient to spurious failures. Nonetheless, you can only perform a finite number of retries; you will have to give up eventually.&lt;&#x2F;p&gt;
&lt;p&gt;Our implementation opts for the second strategy from the get-go. It might result in a few more 500s, but it is not incompatible with our over-arching objective.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-2-2-postmark-api-errors&quot;&gt;3.2.2. Postmark - API Errors&lt;&#x2F;h3&gt;
&lt;p&gt;What about email delivery issues?&lt;br &#x2F;&gt;
Let&#x27;s start with the simplest scenario: Postmark returns an error when we try to email one of our subscribers.&lt;br &#x2F;&gt;
Our current implementation bails out: we abort the processing and return a &lt;code&gt;500 Internal Server Error&lt;&#x2F;code&gt; to the caller.&lt;&#x2F;p&gt;
&lt;p&gt;We are sending emails out sequentially. We will never get a chance to deliver the new issue to the subscribers at the end of the list if we abort as soon as an API error is encountered. This is far from being &quot;best-effort delivery&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;This is not the end of our problems either - can the newsletter author retry the form submission?&lt;&#x2F;p&gt;
&lt;p&gt;It depends on &lt;strong&gt;where&lt;&#x2F;strong&gt; the error occurred.&lt;&#x2F;p&gt;
&lt;p&gt;Was it the first subscriber in the list returned by our database query?&lt;br &#x2F;&gt;
No problem, nothing has happened yet.&lt;&#x2F;p&gt;
&lt;p&gt;What if it were the third subscriber in the list? Or the fifth? Or the one-hundredth?&lt;br &#x2F;&gt;
We have a problem: some subscribers have been sent the new issue, others haven&#x27;t.&lt;br &#x2F;&gt;
If the author retries, some subscribers are going to receive the issue &lt;strong&gt;twice&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
If they don&#x27;t retry, some subscribers might never receive the issue.&lt;&#x2F;p&gt;
&lt;p&gt;Damned if you do, damned if you don&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;You might recognize the struggle: we are dealing with a &lt;strong&gt;workflow&lt;&#x2F;strong&gt;, a combination of multiple &lt;strong&gt;sub-tasks&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
We faced something similar in chapter 7 when we had to execute a sequence of SQL queries to create a new subscriber. Back then, we opted for an all-or-nothing semantics using SQL transactions: nothing happens unless all queries succeed.
Postmark&#x27;s API does not provide any&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#postmark-batch&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; kind of transactional semantics - each API call is its own unit of work, we have no way to link them together.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-3-application-crashes&quot;&gt;3.3. Application Crashes&lt;&#x2F;h2&gt;
&lt;p&gt;Our application could crash at any point in time. It might, for example, run out of memory or the server it is running on might be abruptly terminated (welcome to the cloud!).&lt;&#x2F;p&gt;
&lt;p&gt;A crash, in particular, might happen &lt;strong&gt;after&lt;&#x2F;strong&gt; we started to process the subscribers list but &lt;strong&gt;before&lt;&#x2F;strong&gt; we got to the end of it. The author will receive an error message in the browser.&lt;br &#x2F;&gt;
Re-submitting the form is likely to result in a high number of redundant deliveries, just like we observed when discussing the consequences of Postmark&#x27;s API errors.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-4-author-actions&quot;&gt;3.4. Author Actions&lt;&#x2F;h2&gt;
&lt;p&gt;Last but not least, we might have issues in the interaction between the author and the API.&lt;&#x2F;p&gt;
&lt;p&gt;If we are dealing with a large audience, it might take minutes to process the entire subscribers list. The author might get impatient and choose to re-submit the form. The browser might decide to give up (client-side timeout). Or, equally problematic, the author might click on the &lt;code&gt;Submit&lt;&#x2F;code&gt; button more than once&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#disable-button&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, by mistake.&lt;&#x2F;p&gt;
&lt;p&gt;Once again, we end up in a corner because our implementation is not retry-safe.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;4-idempotency-an-introduction&quot;&gt;4. Idempotency: An Introduction&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; is, all things considered, a pretty simple endpoint. Nonetheless, our investigation highlighted several scenarios where the current implementation fails to meet our expectations.&lt;br &#x2F;&gt;
Most of our problems boil down to a specific limitation: &lt;strong&gt;it is not safe to retry&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Retry-safety has a dramatic impact on the ergonomics of an API. It is substantially easier to write a reliable API client if you can safely retry when something goes wrong.&lt;br &#x2F;&gt;
But what does retry-safety &lt;strong&gt;actually&lt;&#x2F;strong&gt; entail?&lt;&#x2F;p&gt;
&lt;p&gt;We built an intuitive understanding of what it means in our domain, newsletter delivery - send the content to every subscriber no more than once. How does that transfer to another domain?&lt;br &#x2F;&gt;
You might be surprised to find out that we do not have a clear industry-accepted definition. It is a tricky subject.&lt;br &#x2F;&gt;
For the purpose of this book, we will define retry-safety as follows:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;An API endpoint is retry-safe (or &lt;strong&gt;idempotent&lt;&#x2F;strong&gt;) if the caller has no way to &lt;strong&gt;observe&lt;&#x2F;strong&gt; if a request has been sent to the server once or multiple times.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We will probe and explore this definition for a few sections: it is important to fully understand its ramifications.&lt;&#x2F;p&gt;
&lt;p&gt;If you have been in the industry long enough, you have probably heard another term used to describe the concept of retry-safety: &lt;strong&gt;idempotency&lt;&#x2F;strong&gt;.
They are mostly used as synonyms - we will use idempotency going forward, mostly to align with other industry terminology that will be relevant to our implementation (i.e. idempotency keys).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-1-idempotency-in-action-payments&quot;&gt;4.1. Idempotency In Action: Payments&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s explore the implications of our idempotency definition in another domain, payments.&lt;br &#x2F;&gt;
Our fictional payments API exposes three endpoints:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET &#x2F;balance&lt;&#x2F;code&gt;, to retrieve your current account balance;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;GET &#x2F;payments&lt;&#x2F;code&gt;, to retrieve the list of payments you initiated;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;POST &#x2F;payments&lt;&#x2F;code&gt;, to initiate a new payment.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;POST &#x2F;payments&lt;&#x2F;code&gt;, in particular, takes as input the beneficiary details and the payment amount. An API call triggers a money transfer from your account to the specified beneficiary; your balance is reduced accordingly (i.e. &lt;code&gt;new_balance = old_balance - payment_amount&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s consider this scenario: your balance is 400 USD and you send a request to transfer 20 USD. The request succeeds: the API returned a &lt;code&gt;200 OK&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#payments&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, your balance was updated to 380 USD and the beneficiary received 20 USD.&lt;br &#x2F;&gt;
You then retry the same request - e.g. you click twice on the &lt;code&gt;Pay now&lt;&#x2F;code&gt; button.&lt;br &#x2F;&gt;
What should happen if &lt;code&gt;POST &#x2F;payments&lt;&#x2F;code&gt; is idempotent?&lt;&#x2F;p&gt;
&lt;p&gt;Our idempotency definition is built around the concept of &lt;em&gt;observability&lt;&#x2F;em&gt; - properties of the system state that the caller can inspect by interacting with the system itself.&lt;br &#x2F;&gt;
For example: you could easily determine that the second call is a retry by going through the logs emitted by the API. But the caller is not an operator - they have no way to inspect those logs. They are &lt;em&gt;invisible&lt;&#x2F;em&gt; to the users of the API - in so far as idempotency is concerned, they don&#x27;t exist. They are not part of the &lt;strong&gt;domain model&lt;&#x2F;strong&gt; exposed and manipulated by the API.&lt;&#x2F;p&gt;
&lt;p&gt;The domain model in our example includes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the caller&#x27;s account, with its balance (via &lt;code&gt;GET &#x2F;balance&lt;&#x2F;code&gt;) and payment history (via &lt;code&gt;GET &#x2F;payments&lt;&#x2F;code&gt;);&lt;&#x2F;li&gt;
&lt;li&gt;other accounts&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#beneficiaries&quot;&gt;6&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; reachable over the payment network (i.e. beneficiaries we can pay).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Given the above, we can say that &lt;code&gt;POST &#x2F;payments&lt;&#x2F;code&gt; is idempotent if, when the request is retried,&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the balance remains 380 USD;&lt;&#x2F;li&gt;
&lt;li&gt;no additional money are transferred to the beneficiary.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There is one more detail to sort out - what HTTP response should the server return for the retried request?&lt;br &#x2F;&gt;
The caller should not be able to observe that the second request was a retry. The payment succeeded, therefore the server should return a success response that is semantically equivalent to the HTTP response used to answer the initial request.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-2-idempotency-keys&quot;&gt;4.2. Idempotency Keys&lt;&#x2F;h2&gt;
&lt;p&gt;There is room for ambiguity in our definition of idempotency: how do we distinguish between a retry and a user trying to perform two distinct payments for the same amount to the same beneficiary?&lt;&#x2F;p&gt;
&lt;p&gt;We need to understand the caller&#x27;s &lt;strong&gt;intent&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We could try to use a heuristic - e.g. the second request is a duplicate if it was sent no more than 5 minutes later.&lt;br &#x2F;&gt;
This could be a good starting point, but it is not bulletproof. The consequences of misclassification could be dire, both for the caller and our reputation as an organization (e.g. a late retry causing a double payment).&lt;&#x2F;p&gt;
&lt;p&gt;Given that this is all about understanding the caller&#x27;s intent, there is no better strategy than empowering the caller themselves to tell us what they are trying to do. This is commonly accomplished using &lt;strong&gt;idempotency keys&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
The caller generates a unique identifier, the idempotency key, for every state-altering operation they want to perform. The idempotency key is attached to the outgoing request, usually as an HTTP header (e.g. &lt;code&gt;Idempotency-Key&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#no-spec-idempotency-key&quot;&gt;7&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;).&lt;br &#x2F;&gt;
The server can now easily spot duplicates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;two identical requests, different idempotency keys = two distinct operations;&lt;&#x2F;li&gt;
&lt;li&gt;two identical requests, same idempotency key = a single operation, the second request is a duplicate;&lt;&#x2F;li&gt;
&lt;li&gt;two different&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fingerprinting&quot;&gt;8&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; requests, same idempotency key = the first request is processed, the second one is rejected.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We will start requiring an idempotency key in &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; as part of our idempotency implementation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-3-concurrent-requests&quot;&gt;4.3. Concurrent Requests&lt;&#x2F;h2&gt;
&lt;p&gt;What should happen when two duplicate requests are fired &lt;strong&gt;concurrently&lt;&#x2F;strong&gt; - i.e. the second request reaches the server before it finishes processing the first one?&lt;&#x2F;p&gt;
&lt;p&gt;We do not yet know the outcome of the first request. Processing both requests in parallel might also introduce the risk of performing side effects more than once (e.g. initiating two distinct payments).&lt;&#x2F;p&gt;
&lt;p&gt;It is common to introduce &lt;strong&gt;synchronization&lt;&#x2F;strong&gt;: the second request should not be processed until the first one has completed.&lt;br &#x2F;&gt;
We have two options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Reject the second request by returning a &lt;code&gt;409 Conflict&lt;&#x2F;code&gt; status code back to the caller;&lt;&#x2F;li&gt;
&lt;li&gt;Wait until the first request completes processing. Then return the same response back to the caller.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Both are viable.&lt;br &#x2F;&gt;
The latter is fully transparent to the caller, making it easier to consume the API - they don&#x27;t have to handle yet another transient failure mode. There is a price&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#dos-409&quot;&gt;9&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; to pay though: both the client and the server need to keep an open connection while spinning idle, waiting for the other task to complete.&lt;br &#x2F;&gt;
Considering our use case (processing forms), we will go for the second strategy in order to minimize the number of user-visible errors - browsers do not automatically retry &lt;code&gt;409&lt;&#x2F;code&gt;s.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;5-requirements-as-tests-1&quot;&gt;5. Requirements As Tests #1&lt;&#x2F;h1&gt;
&lt;p&gt;Let&#x27;s start by focusing on the simplest scenario: a request was received and processed successfully, then a retry is performed.&lt;br &#x2F;&gt;
We expect a success response with no duplicate newsletter delivery:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;newsletter.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletter_creation_is_idempotent&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Arrange
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; app = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn_app&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;create_confirmed_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;    app.test_user.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;login&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Mock::given(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;email&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;method&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;respond_with&lt;&#x2F;span&gt;&lt;span&gt;(ResponseTemplate::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;mount&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app.email_server)
&lt;&#x2F;span&gt;&lt;span&gt;        .await;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Part 1 - Submit newsletter form
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_request_body = serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Newsletter title&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Newsletter body as plain text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;Newsletter body as HTML&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We expect the idempotency key as part of the
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; form data, not as an header
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: uuid::Uuid::new_v4().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;post_publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;newsletter_request_body).await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;assert_is_redirect_to&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;response, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&#x2F;newsletters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Part 2 - Follow the redirect
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; html_page = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_publish_newsletter_html&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    assert!(
&lt;&#x2F;span&gt;&lt;span&gt;        html_page.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;i&amp;gt;The newsletter issue has been published!&amp;lt;&#x2F;i&amp;gt;&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Part 3 - Submit newsletter form **again**
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;post_publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;newsletter_request_body).await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;assert_is_redirect_to&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;response, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&#x2F;newsletters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Part 4 - Follow the redirect
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; html_page = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_publish_newsletter_html&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    assert!(
&lt;&#x2F;span&gt;&lt;span&gt;        html_page.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;i&amp;gt;The newsletter issue has been published!&amp;lt;&#x2F;i&amp;gt;&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we have sent the newsletter email **once**
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;cargo test&lt;&#x2F;code&gt; should fail:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;thread &amp;#39;newsletter::newsletter_creation_is_idempotent&amp;#39; panicked at 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;Verifications&lt;&#x2F;span&gt;&lt;span&gt; failed:
&lt;&#x2F;span&gt;&lt;span&gt;- Mock #&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.
&lt;&#x2F;span&gt;&lt;span&gt;    Expected range of matching incoming requests: == &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;span&gt;    Number of matched incoming requests: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2
&lt;&#x2F;span&gt;&lt;span&gt;[...]&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The retry succeeded, but it resulted in the newsletter being delivered twice to our subscriber - the problematic behaviour we identified during the failure analysis at the very beginning of this chapter.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;6-implementation-strategies&quot;&gt;6. Implementation Strategies&lt;&#x2F;h1&gt;
&lt;p&gt;How do we prevent the retried request from dispatching a new round of emails to our subscribers?
We have two options - one requires state, the other doesn&#x27;t.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;6-1-stateful-idempotency-save-and-replay&quot;&gt;6.1. Stateful Idempotency: Save And Replay&lt;&#x2F;h2&gt;
&lt;p&gt;In the stateful approach, we process the first request and then store its idempotency key next to the HTTP response we are about to return. When a retry comes in, we look for a match in the store against its idempotency key, fetch the saved HTTP response and return it to the caller.&lt;br &#x2F;&gt;
The entire handler logic is short-circuited - it never gets executed. Postmark&#x27;s API is never called again, preventing duplicate deliveries.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;6-2-stateless-idempotency-deterministic-key-generation&quot;&gt;6.2. Stateless Idempotency: Deterministic Key Generation&lt;&#x2F;h2&gt;
&lt;p&gt;The stateless approach tries to achieve the same outcome without relying on persistence.&lt;br &#x2F;&gt;
For every subscriber, we &lt;strong&gt;deterministically&lt;&#x2F;strong&gt; generate a new idempotency key using their subscriber id, the newsletter content&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#fingerprint-generation&quot;&gt;10&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and the idempotency key attached to the incoming request. Every time we call Postmark to send an email we make sure to pass along the subscriber-specific idempotency key.&lt;br &#x2F;&gt;
When a retry comes in, we execute the same processing logic - this leads to the same sequence of HTTP calls to Postmark, using exactly the same idempotency keys. Assuming their idempotency implementation is sound, no new email is going to be dispatched.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;6-3-time-is-a-tricky-beast&quot;&gt;6.3. Time Is a Tricky Beast&lt;&#x2F;h2&gt;
&lt;p&gt;The stateless and the stateful approach are not 100% equivalent.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s consider what happens, for example, when a new person subscribes to our newsletter between the initial request and the following retry.&lt;br &#x2F;&gt;
The stateless approach executes the handler logic in order to process the retried request. In particular, it re-generates the list of &lt;em&gt;current&lt;&#x2F;em&gt; subscribers before kicking off the email dispatching &lt;code&gt;for&lt;&#x2F;code&gt; loop. As a result, the new subscriber will receive the newsletter issue.&lt;br &#x2F;&gt;
This is not the case when following the stateful approach - we retrieve the HTTP response from the store and return it to the caller without performing any kind of processing.&lt;&#x2F;p&gt;
&lt;p&gt;This is a symptom of a deeper discrepancy - the &lt;strong&gt;elapsed time&lt;&#x2F;strong&gt; between the initial request and the following retry affects the processing outcome when following the stateless approach.&lt;br &#x2F;&gt;
We cannot execute our handler logic against the same snapshot of the state seen by the first request - therefore, the view of the world in the stateless approach is impacted by all the operations that have been committed since the first request was processed&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#non-repeatable-read&quot;&gt;11&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; (e.g. new subscribers joining the mailing list).&lt;&#x2F;p&gt;
&lt;p&gt;Whether this is acceptable or not depends on the domain.&lt;br &#x2F;&gt;
In our case, the fallout is quite minor - we are just sending extra newsletters out. We could live with it if the stateless approach led us to a dramatically simpler implementation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;6-4-making-a-choice&quot;&gt;6.4. Making A Choice&lt;&#x2F;h2&gt;
&lt;p&gt;Unfortunately, the circumstances leave us with no wiggle room: Postmark&#x27;s API does not provide any idempotency mechanism therefore we cannot follow the stateless approach.&lt;br &#x2F;&gt;
The stateful approach happens to be trickier to implement - rejoice, we&#x27;ll have a chance to learn some new patterns!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;7-idempotency-store&quot;&gt;7. Idempotency Store&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;7-1-which-database-should-we-use&quot;&gt;7.1. Which Database Should We Use?&lt;&#x2F;h2&gt;
&lt;p&gt;For each idempotency key, we must store the associated HTTP response.&lt;br &#x2F;&gt;
Our application currently uses two different data sources:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Redis, to store the session state for each user;&lt;&#x2F;li&gt;
&lt;li&gt;Postgres, for everything else.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We do not want to store idempotency keys forever - it would be impractical and wasteful.&lt;br &#x2F;&gt;
We also do not want actions performed by a user A to influence the outcome of actions performed by user B - there is a concrete security risk (cross-user data leakage) if proper isolation is not enforced.&lt;&#x2F;p&gt;
&lt;p&gt;Storing idempotency keys and responses into the session state of the user would guarantee both isolation and expiry out of the box. At the same time, it doesn&#x27;t feel right to tie the lifespan of idempotency keys to the lifespan of the corresponding user sessions.&lt;&#x2F;p&gt;
&lt;p&gt;Based on our current requirements, Redis looks like the best solution to store our &lt;code&gt;(user_id, idempotency_key, http_response)&lt;&#x2F;code&gt; triplets. They would have their own time-to-live policy, with no ties to session states, and Redis would take care of cleaning old entries for us.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, new requirements will soon emerge and turn Redis into a limiting choice. There is not much to learn by taking the wrong turn here, so I&#x27;ll cheat and force our hand towards Postgres.&lt;br &#x2F;&gt;
Spoiler: we will leverage the possibility of modifying the idempotency triplets and our application state within a single SQL transaction.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;7-2-schema&quot;&gt;7.2. Schema&lt;&#x2F;h2&gt;
&lt;p&gt;We need to define a new table to store the following information:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;user id;&lt;&#x2F;li&gt;
&lt;li&gt;idempotency key;&lt;&#x2F;li&gt;
&lt;li&gt;HTTP response.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The user id and the idempotency key can be used as a composite primary key. We should also record when each row was created in order to evict old idempotency keys.&lt;&#x2F;p&gt;
&lt;p&gt;There is a major unknown though: what type should be used to store HTTP responses?&lt;&#x2F;p&gt;
&lt;p&gt;We could treat the whole HTTP response as a blob of bytes, using &lt;code&gt;bytea&lt;&#x2F;code&gt; as column type.&lt;br &#x2F;&gt;
Unfortunately, it&#x27;d be tricky to re-hydrate the bytes into an &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; object - &lt;code&gt;actix-web&lt;&#x2F;code&gt; does not provide any serialization&#x2F;deserialization implementation for &lt;code&gt;HttpResponse&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
We are going to write our own (de)serialisation code - we will work with the core components of an HTTP response:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;status code;&lt;&#x2F;li&gt;
&lt;li&gt;headers;&lt;&#x2F;li&gt;
&lt;li&gt;body.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;We are not going to store the HTTP version - the assumption is that we are working exclusively with &lt;code&gt;HTTP&#x2F;1.1&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
We can use &lt;code&gt;smallint&lt;&#x2F;code&gt; for the status code - it&#x27;s maximum value is 32767, which is more than enough. &lt;code&gt;bytea&lt;&#x2F;code&gt; will do for the body.&lt;br &#x2F;&gt;
What about headers? What is their type?&lt;&#x2F;p&gt;
&lt;p&gt;We can have multiple header values associated to the same header name, therefore it makes sense to represent them as an array of &lt;code&gt;(name, value)&lt;&#x2F;code&gt; pairs.&lt;br &#x2F;&gt;
We can use &lt;code&gt;TEXT&lt;&#x2F;code&gt; for the &lt;code&gt;name&lt;&#x2F;code&gt; (see &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hyperium&#x2F;http&#x2F;blob&#x2F;c28945c6c6f99379b674a1e961a743c7752f2346&#x2F;src&#x2F;header&#x2F;name.rs#L981&quot;&gt;&lt;code&gt;http&lt;&#x2F;code&gt;&#x27;s implementation&lt;&#x2F;a&gt;) while &lt;code&gt;value&lt;&#x2F;code&gt; will require &lt;code&gt;BYTEA&lt;&#x2F;code&gt; because it allows opaque octets (see &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hyperium&#x2F;http&#x2F;blob&#x2F;c28945c6c6f99379b674a1e961a743c7752f2346&#x2F;src&#x2F;header&#x2F;value.rs#L780&quot;&gt;&lt;code&gt;http&lt;&#x2F;code&gt;&#x27;s test cases&lt;&#x2F;a&gt;).&lt;br &#x2F;&gt;
Postgres does not support arrays of tuples, but there is a workaround: we can define a Postgres &lt;strong&gt;composite type&lt;&#x2F;strong&gt; - i.e. a named collection of fields, the equivalent of a struct in our Rust code.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;CREATE TYPE &lt;&#x2F;span&gt;&lt;span&gt;header_pair &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; (
&lt;&#x2F;span&gt;&lt;span&gt;    name &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    value &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;BYTEA
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now put together the migration script:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate add create_idempotency_table
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;-- migrations&#x2F;20220211080603_create_idempotency_table.sql
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;CREATE TYPE &lt;&#x2F;span&gt;&lt;span&gt;header_pair &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;AS&lt;&#x2F;span&gt;&lt;span&gt; (
&lt;&#x2F;span&gt;&lt;span&gt;    name &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    value &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;BYTEA
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;CREATE TABLE &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;idempotency&lt;&#x2F;span&gt;&lt;span&gt; (
&lt;&#x2F;span&gt;&lt;span&gt;   user_id uuid NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;REFERENCES&lt;&#x2F;span&gt;&lt;span&gt; users(user_id),
&lt;&#x2F;span&gt;&lt;span&gt;   idempotency_key &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   response_status_code &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;SMALLINT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   response_headers header_pair[] NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   response_body &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;BYTEA &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   created_at &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;timestamptz &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;(user_id, idempotency_key)
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We could have defined an overall &lt;code&gt;http_response&lt;&#x2F;code&gt; composite type, but we would have run into &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;launchbadge&#x2F;sqlx&#x2F;issues&#x2F;1031&quot;&gt;a bug in &lt;code&gt;sqlx&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; which is in turn caused by a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust&#x2F;issues&#x2F;82219&quot;&gt;bug in the Rust compiler&lt;&#x2F;a&gt;. Best to avoid nested composite types for the time being.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;8-save-and-replay&quot;&gt;8. Save And Replay&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;8-1-read-idempotency-key&quot;&gt;8.1. Read Idempotency Key&lt;&#x2F;h2&gt;
&lt;p&gt;Our &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; endpoint is being triggered by an HTML form submission, therefore we do not have control over the headers that are being sent to the server.&lt;br &#x2F;&gt;
The most practical choice is to embed the idempotency key inside the form data:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(serde::Deserialize)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;FormData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; New field!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;: String
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We do not care about the exact format of the idempotency key, as long as it&#x27;s not empty and it&#x27;s reasonably long.&lt;br &#x2F;&gt;
Let&#x27;s define a new type to enforce minimal validation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;lib.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; New module!
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub mod &lt;&#x2F;span&gt;&lt;span&gt;idempotency;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;mod.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;key;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub use &lt;&#x2F;span&gt;&lt;span&gt;key::IdempotencyKey;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;key.rs
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;IdempotencyKey(String);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;TryFrom&amp;lt;String&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;IdempotencyKey {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Error = anyhow::Error;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_from&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span&gt;: String) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; s.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_empty&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            anyhow::bail!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;The idempotency key cannot be empty&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; max_length = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; s.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;() &amp;gt;= max_length {
&lt;&#x2F;span&gt;&lt;span&gt;            anyhow::bail!(
&lt;&#x2F;span&gt;&lt;span&gt;                &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;The idempotency key must be shorter 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;                than {max_length} characters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;(s))
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;From&amp;lt;IdempotencyKey&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;String {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;k&lt;&#x2F;span&gt;&lt;span&gt;: IdempotencyKey) -&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        k.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;AsRef&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;IdempotencyKey {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now use it in &lt;code&gt;publish_newsletter&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;utils.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;actix_web::http::StatusCode;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Return a 400 with the user-representation of the validation error as body.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; The error root cause is preserved for logging purposes.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;e400&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;T&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;: T) -&amp;gt; actix_web::Error
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;where
&lt;&#x2F;span&gt;&lt;span&gt;    T: std::fmt::Debug + std::fmt::Display + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static
&lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    actix_web::error::ErrorBadRequest(e)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::idempotency::IdempotencyKey;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::utils::e400;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We must destructure the form to avoid upsetting the borrow-checker
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; FormData { title, text_content, html_content, idempotency_key } = form.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; idempotency_key: IdempotencyKey = idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e400)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; subscribers = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool).await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; subscriber in subscribers {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; subscriber {
&lt;&#x2F;span&gt;&lt;span&gt;            Ok(subscriber) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; No longer using `form.&amp;lt;X&amp;gt;`
&lt;&#x2F;span&gt;&lt;span&gt;                email_client
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send_email&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;subscriber.email, &amp;amp;title, &amp;amp;html_content, &amp;amp;text_content)
&lt;&#x2F;span&gt;&lt;span&gt;                    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Success! The idempotency key has been parsed and validated.&lt;br &#x2F;&gt;
Some of our old tests, though, are not particularly happy:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;thread &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;newsletter::you_must_be_logged_in_to_publish_a_newsletter&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;panicked&lt;&#x2F;span&gt;&lt;span&gt; at &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;assertion failed: `(left == right)`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;  left: `400`,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; right: `303`&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;thread &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;newsletter::newsletters_are_not_delivered_to_unconfirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;panicked&lt;&#x2F;span&gt;&lt;span&gt; at &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;assertion failed: `(left == right)`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;  left: `400`,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; right: `303`&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;thread &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;newsletter::newsletters_are_delivered_to_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;panicked&lt;&#x2F;span&gt;&lt;span&gt; at &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;assertion failed: `(left == right)`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;  left: `400`,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; right: `303`&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Our test requests are being rejected because they do not include an idempotency key.&lt;br &#x2F;&gt;
Let&#x27;s update them:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;newsletter.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletters_are_not_delivered_to_unconfirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_request_body = serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: uuid::Uuid::new_v4().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletters_are_delivered_to_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...] 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_request_body = serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: uuid::Uuid::new_v4().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;you_must_be_logged_in_to_publish_a_newsletter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_request_body = serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: uuid::Uuid::new_v4().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Those three tests should now pass again, leaving &lt;code&gt;newsletter::newsletter_creation_is_idempotent&lt;&#x2F;code&gt; as the only failing test.&lt;&#x2F;p&gt;
&lt;p&gt;We also need to update &lt;code&gt;GET &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; to embed a randomly-generated idempotency key in the HTML form:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;get.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter_form&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; idempotency_key = uuid::Uuid::new_v4();
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(HttpResponse::Ok()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;content_type&lt;&#x2F;span&gt;&lt;span&gt;(ContentType::html())
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;(format!(
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;quot;&amp;lt;!-- ... --&amp;gt;   
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;    &amp;lt;form action=&amp;quot;&#x2F;admin&#x2F;newsletters&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &amp;lt;!-- ... --&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &amp;lt;input hidden type=&amp;quot;text&amp;quot; name=&amp;quot;idempotency_key&amp;quot; value=&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{idempotency_key}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;quot;&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Publish&amp;lt;&#x2F;button&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;    &amp;lt;&#x2F;form&amp;gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;    &amp;lt;!-- ... --&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        )))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;8-2-retrieve-saved-responses&quot;&gt;8.2. Retrieve Saved Responses&lt;&#x2F;h2&gt;
&lt;p&gt;The next step is trying to fetch a saved HTTP response from the store, assuming one exists.&lt;&#x2F;p&gt;
&lt;p&gt;It boils down to a single SQL query:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;mod.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mod &lt;&#x2F;span&gt;&lt;span&gt;persistence;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub use &lt;&#x2F;span&gt;&lt;span&gt;persistence::get_saved_response;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use super&lt;&#x2F;span&gt;&lt;span&gt;::IdempotencyKey;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;actix_web::HttpResponse;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;sqlx::PgPool;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;uuid::Uuid;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_saved_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;IdempotencyKey,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Option&amp;lt;HttpResponse&amp;gt;, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SELECT 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_status_code, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_headers,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_body
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        FROM idempotency
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        WHERE 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;          user_id = $1 AND
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;          idempotency_key = $2
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        user_id,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fetch_optional&lt;&#x2F;span&gt;&lt;span&gt;(pool)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    todo!()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There is a caveat - &lt;code&gt;sqlx&lt;&#x2F;code&gt; does not know how to handle our custom &lt;code&gt;header_pair&lt;&#x2F;code&gt; type:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;error:&lt;&#x2F;span&gt;&lt;span&gt; unsupported type _header_pair of column &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#2 (&amp;quot;response_headers&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt; |
&lt;&#x2F;span&gt;&lt;span&gt; |       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt; |  __________________________^
&lt;&#x2F;span&gt;&lt;span&gt; | |         r#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         SELECT 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.. |
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         idempotency_key.as_ref()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |     )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;   | |_____^
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It might not be supported out of the box, but there is a mechanism for us to specify how it should be handled - the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;sqlx&#x2F;0.5.10&#x2F;sqlx&#x2F;trait.Type.html&quot;&gt;&lt;code&gt;Type&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;sqlx&#x2F;0.5.10&#x2F;sqlx&#x2F;trait.Decode.html&quot;&gt;&lt;code&gt;Decode&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;sqlx&#x2F;0.5.10&#x2F;sqlx&#x2F;trait.Encode.html&quot;&gt;&lt;code&gt;Encode&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; traits.&lt;br &#x2F;&gt;
Luckily enough, we do not have to implement them manually - we can derive them with a macro!&lt;br &#x2F;&gt;
We just need to specify the type fields and the name of the composite type as it appears in Postgres; the macro should take care of the rest:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Debug, sqlx::Type)]
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt;(type_name = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;header_pair&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;HeaderPairRecord {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Unfortunately, the error is still there.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;error:&lt;&#x2F;span&gt;&lt;span&gt; unsupported type _header_pair of column &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#2 (&amp;quot;response_headers&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt; |
&lt;&#x2F;span&gt;&lt;span&gt; |       &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt; |  __________________________^
&lt;&#x2F;span&gt;&lt;span&gt; | |         r#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         SELECT 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.. |
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         idempotency_key.as_ref()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |     )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |_____^
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;&#x2F; [...] &amp;lt;new error&amp;gt; [...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It turns out that &lt;code&gt;sqlx::query!&lt;&#x2F;code&gt; does not handle custom type automatically - we need to explain how we want the custom column to be handled by using an explicit type annotation.&lt;br &#x2F;&gt;
The query becomes:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_saved_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SELECT 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_status_code, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_headers as &amp;quot;response_headers: Vec&amp;lt;HeaderPairRecord&amp;gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_body
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At last, it compiles!&lt;br &#x2F;&gt;
Let&#x27;s map the retrieved data back into a proper &lt;code&gt;HttpResponse&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;actix_web::http::StatusCode;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_saved_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Option&amp;lt;HttpResponse&amp;gt;, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = sqlx::query!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fetch_optional&lt;&#x2F;span&gt;&lt;span&gt;(pool)
&lt;&#x2F;span&gt;&lt;span&gt;        .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;Some(r) = saved_response {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; status_code = StatusCode::from_u16(
&lt;&#x2F;span&gt;&lt;span&gt;            r.response_status_code.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span&gt;()?
&lt;&#x2F;span&gt;&lt;span&gt;        )?;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; response = HttpResponse::build(status_code);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; HeaderPairRecord { name, value } in r.response_headers {
&lt;&#x2F;span&gt;&lt;span&gt;            response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;append_header&lt;&#x2F;span&gt;&lt;span&gt;((name, value));
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(Some(response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;(r.response_body)))
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(None)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now plug &lt;code&gt;get_saved_response&lt;&#x2F;code&gt; into our request handler:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...] 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::idempotency::get_saved_response;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Inject the user id extracted from the user session
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;: ReqData&amp;lt;UserId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; user_id = user_id.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into_inner&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; FormData {
&lt;&#x2F;span&gt;&lt;span&gt;        title,
&lt;&#x2F;span&gt;&lt;span&gt;        text_content,
&lt;&#x2F;span&gt;&lt;span&gt;        html_content,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key,
&lt;&#x2F;span&gt;&lt;span&gt;    } = form.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; idempotency_key: IdempotencyKey = idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e400)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Return early if we have a saved response in the database 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;Some(saved_response) = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_saved_response&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;idempotency_key, *user_id)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Ok(saved_response);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;8-3-save-responses&quot;&gt;8.3. Save Responses&lt;&#x2F;h2&gt;
&lt;p&gt;We have code to retrieve saved responses, but we don&#x27;t have code yet to save responses - that&#x27;s what we will be focusing on next.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s add a new function skeleton to our &lt;code&gt;idempotency&lt;&#x2F;code&gt; module:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;mod.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub use &lt;&#x2F;span&gt;&lt;span&gt;persistence::save_response;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;IdempotencyKey,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_user_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;_http_response&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;HttpResponse
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    todo!()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We need to break &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; into its separate components before we write the &lt;code&gt;INSERT&lt;&#x2F;code&gt; query.&lt;br &#x2F;&gt;
We can use &lt;code&gt;.status()&lt;&#x2F;code&gt; for the status code, &lt;code&gt;.headers()&lt;&#x2F;code&gt; for the headers... what about the body?&lt;br &#x2F;&gt;
There is a &lt;code&gt;.body()&lt;&#x2F;code&gt; method - this is its signature:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; Returns a reference to this response&amp;#39;s body.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;B {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.res.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;What is &lt;code&gt;B&lt;&#x2F;code&gt;? We must include the &lt;code&gt;impl&lt;&#x2F;code&gt; block definition into the picture to grasp it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;B&amp;gt; HttpResponse&amp;lt;B&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; Returns a reference to this response&amp;#39;s body.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;B {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.res.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well, well, it turns out that &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; is generic over the body type!&lt;br &#x2F;&gt;
But, you may ask, &quot;we have been using &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; for 400 pages without specifying any generic parameter, what&#x27;s going on?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;There is a default generic parameter which kicks in if &lt;code&gt;B&lt;&#x2F;code&gt; is left unspecified:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; An outgoing response.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;HttpResponse&amp;lt;B = BoxBody&amp;gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;8-3-1-messagebody-and-http-streaming&quot;&gt;8.3.1. &lt;code&gt;MessageBody&lt;&#x2F;code&gt; and HTTP Streaming&lt;&#x2F;h3&gt;
&lt;p&gt;Why does &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; need to be generic over the body type in the first place? Can&#x27;t it just use &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;&#x2F;code&gt; or a similar bytes container?&lt;&#x2F;p&gt;
&lt;p&gt;We have always worked with responses that were fully formed on the server before being sent back to the caller. HTTP&#x2F;1.1 supports another mechanism to transfer data - &lt;code&gt;Transfer-Encoding: chunked&lt;&#x2F;code&gt;, also known as &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;CMCDragonkai&#x2F;6bfade6431e9ffb7fe88&quot;&gt;&lt;strong&gt;HTTP streaming&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
The server breaks down the payload into multiple chunks and sends them over to the caller one at a time instead of accumulating the entire body in memory first. It allows the server to significantly reduce its memory usage. It is quite useful when working on large payloads such as files or results from a large query (streaming all the way through!).&lt;&#x2F;p&gt;
&lt;p&gt;With HTTP streaming in mind, it becomes easier to understand the design of &lt;code&gt;MessageBody&lt;&#x2F;code&gt;, the trait that must be implemented to use a type as body in &lt;code&gt;actix-web&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span&gt;MessageBody {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;Error: Into&amp;lt;Box&amp;lt;dyn Error + &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;, Global&amp;gt;&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; BodySize;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;poll_next&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;: Pin&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut Self&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;, 
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;cx&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span&gt;Context&amp;lt;&amp;#39;_&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; Poll&amp;lt;Option&amp;lt;Result&amp;lt;Bytes, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self::&lt;&#x2F;span&gt;&lt;span&gt;Error&amp;gt;&amp;gt;&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You pull data, one chunk at a time, until you have fetched it all.&lt;br &#x2F;&gt;
When the response is not being streamed, the data is available all at once - &lt;code&gt;poll_next&lt;&#x2F;code&gt; returns it all in one go.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s try to understand &lt;code&gt;BoxBody&lt;&#x2F;code&gt;, the default body type used by &lt;code&gt;HttpResponse&lt;&#x2F;code&gt;. The body type we have been using for several chapters, unknowingly!&lt;br &#x2F;&gt;
&lt;code&gt;BoxBody&lt;&#x2F;code&gt; abstracts away the specific payload delivery mechanism. Under the hood, it is nothing more than an &lt;code&gt;enum&lt;&#x2F;code&gt; with a variant for each strategy, with a special case catering for body-less responses:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;BoxBody(BoxBodyInner);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;BoxBodyInner {
&lt;&#x2F;span&gt;&lt;span&gt;    None(body::None),
&lt;&#x2F;span&gt;&lt;span&gt;    Bytes(Bytes),
&lt;&#x2F;span&gt;&lt;span&gt;    Stream(Pin&amp;lt;Box&amp;lt;dyn MessageBody&amp;lt;Error = Box&amp;lt;dyn StdError&amp;gt;&amp;gt;&amp;gt;&amp;gt;),
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It worked for so long because we did not really care about the way the response was being sent back to the caller.&lt;br &#x2F;&gt;
Implementing &lt;code&gt;save_response&lt;&#x2F;code&gt; forces us to look closer - we need to collect the response in memory&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#in-memory-or-streaming&quot;&gt;12&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; in order to save it in the &lt;code&gt;idempotency&lt;&#x2F;code&gt; table of our database.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;actix-web&lt;&#x2F;code&gt; has a dedicated function for situation like ours: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;actix-web&#x2F;4.0.0-rc.3&#x2F;actix_web&#x2F;body&#x2F;fn.to_bytes.html&quot;&gt;&lt;code&gt;to_bytes&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
It calls &lt;code&gt;poll_next&lt;&#x2F;code&gt; until there is no more data to fetch, than it returns the entire response back to us inside a &lt;code&gt;Bytes&lt;&#x2F;code&gt; container&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#bytes-vec&quot;&gt;13&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;br &#x2F;&gt;
I&#x27;d normally advise for caution when talking about &lt;code&gt;to_bytes&lt;&#x2F;code&gt; - if you are dealing with huge payloads, there is a risk of putting the server under significant memory pressure.&lt;br &#x2F;&gt;
This is not our case - all our response bodies are small and don&#x27;t actually take advantage of HTTP streaming, so &lt;code&gt;to_bytes&lt;&#x2F;code&gt; will not actually do any work.&lt;&#x2F;p&gt;
&lt;p&gt;Enough with the theory - let&#x27;s piece it together:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;actix_web::body::to_bytes;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;IdempotencyKey,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http_response&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;HttpResponse,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; status_code = http_response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_u16&lt;&#x2F;span&gt;&lt;span&gt;() as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i16&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; headers = {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; h = Vec::with_capacity(http_response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(name, value) in http_response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; name = name.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_str&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_owned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; value = value.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_bytes&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_owned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            h.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(HeaderPairRecord { name, value });
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        h
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; body = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_bytes&lt;&#x2F;span&gt;&lt;span&gt;(http_response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;()).await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    todo!()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The compiler is not happy:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;error[E0277]:&lt;&#x2F;span&gt;&lt;span&gt; the trait bound `&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BoxBody:&lt;&#x2F;span&gt;&lt;span&gt; MessageBody` is not satisfied
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span&gt; |
&lt;&#x2F;span&gt;&lt;span&gt; |     &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; body = to_bytes(http_response.body()).await.unwrap();
&lt;&#x2F;span&gt;&lt;span&gt; |                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;--------&lt;&#x2F;span&gt;&lt;span&gt; ^^^^^^^^^^^^^^^^^^^^ 
&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;the&lt;&#x2F;span&gt;&lt;span&gt; trait `&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;MessageBody&lt;&#x2F;span&gt;&lt;span&gt;` is not implemented for `&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;BoxBody&lt;&#x2F;span&gt;&lt;span&gt;`
&lt;&#x2F;span&gt;&lt;span&gt; |                |
&lt;&#x2F;span&gt;&lt;span&gt; |                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;required&lt;&#x2F;span&gt;&lt;span&gt; by a bound introduced by this call
&lt;&#x2F;span&gt;&lt;span&gt; |
&lt;&#x2F;span&gt;&lt;span&gt; = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;help:&lt;&#x2F;span&gt;&lt;span&gt; the following implementations were found:
&lt;&#x2F;span&gt;&lt;span&gt;           &amp;lt;BoxBody &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;as&lt;&#x2F;span&gt;&lt;span&gt; MessageBody&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;BoxBody&lt;&#x2F;code&gt; implements &lt;code&gt;MessageBody&lt;&#x2F;code&gt;, but &lt;code&gt;&amp;amp;BoxBody&lt;&#x2F;code&gt; doesn&#x27;t - and &lt;code&gt;.body()&lt;&#x2F;code&gt; returns a reference, it does not give us ownership over the body.&lt;br &#x2F;&gt;
Why do we need ownership? It&#x27;s because of HTTP streaming, once again!&lt;&#x2F;p&gt;
&lt;p&gt;Pulling a chunk of data from the payload stream requires a mutable reference to the stream itself - once the chunk has been read, there is no way to &quot;replay&quot; the stream and read it again.&lt;&#x2F;p&gt;
&lt;p&gt;There is a common pattern to work around this:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Get ownership of the body via &lt;code&gt;.into_parts()&lt;&#x2F;code&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;Buffer the whole body in memory via &lt;code&gt;to_bytes&lt;&#x2F;code&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Do whatever you have to do with the body&lt;&#x2F;em&gt;;&lt;&#x2F;li&gt;
&lt;li&gt;Re-assemble the response using &lt;code&gt;.set_body()&lt;&#x2F;code&gt; on the request head.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;.into_parts()&lt;&#x2F;code&gt; requires ownership of &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; - we&#x27;ll have to change the signature of &lt;code&gt;save_response&lt;&#x2F;code&gt; to accommodate it. Instead of asking for a reference, we&#x27;ll take ownership of the response and then return another owned &lt;code&gt;HttpResponse&lt;&#x2F;code&gt; in case of success.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s go for it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; No longer a reference!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;http_response&lt;&#x2F;span&gt;&lt;span&gt;: HttpResponse,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(response_head, body) = http_response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into_parts&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `MessageBody::Error` is not `Send` + `Sync`, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; therefore it doesn&amp;#39;t play nicely with `anyhow`
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; body = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_bytes&lt;&#x2F;span&gt;&lt;span&gt;(body).await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;| anyhow::anyhow!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, e))?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; status_code = response_head.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_u16&lt;&#x2F;span&gt;&lt;span&gt;() as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i16&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; headers = {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; h = Vec::with_capacity(response_head.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(name, value) in response_head.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; name = name.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_str&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_owned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; value = value.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_bytes&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_owned&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            h.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(HeaderPairRecord { name, value });
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        h
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; TODO: SQL query
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We need `.map_into_boxed_body` to go from 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; `HttpResponse&amp;lt;Bytes&amp;gt;` to `HttpResponse&amp;lt;BoxBody&amp;gt;`
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; http_response = response_head.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_body&lt;&#x2F;span&gt;&lt;span&gt;(body).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_into_boxed_body&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(http_response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That should compile, although it isn&#x27;t particularly useful (yet).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;8-3-2-array-of-composite-postgres-types&quot;&gt;8.3.2. Array Of Composite Postgres Types&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s add the insertion query:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        INSERT INTO idempotency (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            user_id, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            idempotency_key, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_status_code, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_headers, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_body,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            created_at
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        VALUES ($1, $2, $3, $4, $5, now())
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        user_id,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        status_code,
&lt;&#x2F;span&gt;&lt;span&gt;        headers,
&lt;&#x2F;span&gt;&lt;span&gt;        body.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(pool)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; http_response = response_head.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_body&lt;&#x2F;span&gt;&lt;span&gt;(body).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_into_boxed_body&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(http_response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Compilation fails with an error:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;error:&lt;&#x2F;span&gt;&lt;span&gt; unsupported type _header_pair for param &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#4
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span&gt; |
&lt;&#x2F;span&gt;&lt;span&gt; | &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;     sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt; | |         r#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         INSERT INTO idempotency (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |             user_id,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.. |
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         body.as_ref()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |     )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;   | |_____^
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It does make sense - we are using a custom type and &lt;code&gt;sqlx::query!&lt;&#x2F;code&gt; is not powerful enough to learn about it at compile-time in order to check our query. We will have to disable compile-time verification - use &lt;code&gt;query_unchecked!&lt;&#x2F;code&gt; instead of &lt;code&gt;query!&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query_unchecked!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We are getting closer - a different error!&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;error[E0277]:&lt;&#x2F;span&gt;&lt;span&gt; the trait bound `&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;HeaderPairRecord:&lt;&#x2F;span&gt;&lt;span&gt; PgHasArrayType` is not satisfied
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span&gt; |
&lt;&#x2F;span&gt;&lt;span&gt; | &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;     sqlx::query_unchecked!(
&lt;&#x2F;span&gt;&lt;span&gt; | |         r#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         INSERT INTO idempotency (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |             user_id,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.. |
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |         body.as_ref()
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |     )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; | |_____^ the trait `&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;PgHasArrayType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;` is not implemented for `&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;HeaderPairRecord&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;`
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;sqlx&lt;&#x2F;code&gt; knows, via our &lt;code&gt;#[sqlx(type_name = &quot;header_pair&quot;)]&lt;&#x2F;code&gt; attribute, the name of the composite type itself. It does not know the name of the type for &lt;em&gt;arrays&lt;&#x2F;em&gt; containing &lt;code&gt;header_pair&lt;&#x2F;code&gt; elements.&lt;br &#x2F;&gt;
Postgres creates an array type implicitly when we run a &lt;code&gt;CREATE TYPE&lt;&#x2F;code&gt; statement - it is simply &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;sql-createtype.html&quot;&gt;the composite type name prefixed by an underscore&lt;&#x2F;a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#truncation-array-type&quot;&gt;14&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We can provide this information to &lt;code&gt;sqlx&lt;&#x2F;code&gt; by implementing the &lt;code&gt;PgHasArrayType&lt;&#x2F;code&gt; trait, just like the compiler suggested:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;sqlx::postgres::PgHasArrayType;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;PgHasArrayType &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;HeaderPairRecord {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;array_type_info&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; sqlx::postgres::PgTypeInfo {
&lt;&#x2F;span&gt;&lt;span&gt;        sqlx::postgres::PgTypeInfo::with_name(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;_header_pair&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The code should finally compile.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;8-3-3-plug-it-in&quot;&gt;8.3.3. Plug It In&lt;&#x2F;h3&gt;
&lt;p&gt;It&#x27;s a milestone, but it is a bit early to cheer - we don&#x27;t know if it works yet. Our integration test is still red.&lt;br &#x2F;&gt;
Let&#x27;s plug &lt;code&gt;save_response&lt;&#x2F;code&gt; into our request handler:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::idempotency::save_response;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; subscriber in subscribers {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    FlashMessage::info(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;The newsletter issue has been published!&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;see_other&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&#x2F;newsletters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;idempotency_key, *user_id, response)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Low and behold, &lt;code&gt;cargo test&lt;&#x2F;code&gt; succeeds! We made it!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;9-concurrent-requests&quot;&gt;9. Concurrent Requests&lt;&#x2F;h1&gt;
&lt;p&gt;We dealt with the &quot;easy&quot; scenario when it comes to idempotency: a request arrives, it&#x27;s fully processed, then a retry comes in.&lt;&#x2F;p&gt;
&lt;p&gt;We will now deal with the more troublesome scenario - the retry arrives before the first request is fully processed.&lt;br &#x2F;&gt;
We expect the second request to be queued behind the first one - once that finishes, it will retrieve the saved HTTP response from the store and return it to the caller.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;9-1-requirements-as-tests-2&quot;&gt;9.1. Requirements As Tests #2&lt;&#x2F;h2&gt;
&lt;p&gt;We can, once again, encode our requirements as tests:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;newsletter.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::time::Duration;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;concurrent_form_submission_is_handled_gracefully&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Arrange
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; app = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn_app&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;create_confirmed_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;    app.test_user.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;login&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Mock::given(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;email&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;method&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Setting a long delay to ensure that the second request 
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; arrives before the first one completes
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;respond_with&lt;&#x2F;span&gt;&lt;span&gt;(ResponseTemplate::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_delay&lt;&#x2F;span&gt;&lt;span&gt;(Duration::from_secs(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;mount&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app.email_server)
&lt;&#x2F;span&gt;&lt;span&gt;        .await;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Submit two newsletter forms concurrently
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_request_body = serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Newsletter title&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Newsletter body as plain text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;Newsletter body as HTML&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: uuid::Uuid::new_v4().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response1 = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;post_publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;newsletter_request_body);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response2 = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;post_publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;newsletter_request_body);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(response1, response2) = tokio::join!(response1, response2);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    assert_eq!(response1.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;(), response2.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;    assert_eq!(response1.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;().await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;(), response2.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span&gt;().await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we have sent the newsletter email **once**
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The test fails - our server returned a &lt;code&gt;500 Internal Server Error&lt;&#x2F;code&gt; to one of the two requests:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;thread &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;newsletter::concurrent_form_submission_is_handled_gracefully&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;panicked&lt;&#x2F;span&gt;&lt;span&gt; at &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;assertion failed: `(left == right)`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;  left: `303`,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt; right: `500`&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The logs explain what happened:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;exception.details: 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt; returned from database: 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;duplicate&lt;&#x2F;span&gt;&lt;span&gt; key value violates unique constraint &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_pkey&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Caused&lt;&#x2F;span&gt;&lt;span&gt; by:
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;duplicate&lt;&#x2F;span&gt;&lt;span&gt; key value violates unique constraint &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_pkey&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The slowest request fails to insert into the &lt;code&gt;idempotency&lt;&#x2F;code&gt; table due to our uniqueness constraint.&lt;br &#x2F;&gt;
The error response is not the only issue: both requests executed the email dispatch code (otherwise we wouldn&#x27;t have seen the constraint violation!), resulting into duplicate delivery.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;9-2-synchronization&quot;&gt;9.2. Synchronization&lt;&#x2F;h2&gt;
&lt;p&gt;The second request is not aware of the first until it tries to insert into the database.&lt;br &#x2F;&gt;
If we want to prevent duplicate delivery, we need to introduce &lt;strong&gt;cross-request synchronization&lt;&#x2F;strong&gt; &lt;em&gt;before&lt;&#x2F;em&gt; we start processing subscribers.&lt;&#x2F;p&gt;
&lt;p&gt;In-memory locks (e.g. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tokio&#x2F;latest&#x2F;tokio&#x2F;sync&#x2F;struct.Mutex.html&quot;&gt;&lt;code&gt;tokio::sync::Mutex&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;) would work if all incoming requests were being served by a single API instance. This is not our case: our API is replicated, therefore the two requests might end up being processed by two different instances.&lt;&#x2F;p&gt;
&lt;p&gt;Our synchronization mechanism will have to live out-of-process - our database being the natural candidate.&lt;br &#x2F;&gt;
Let&#x27;s think about it: we have an &lt;code&gt;idempotency&lt;&#x2F;code&gt; table, it contains one row for each unique combination of user id and idempotency key. Can we do something with it?&lt;&#x2F;p&gt;
&lt;p&gt;Our current implementation inserts a row into the &lt;code&gt;idempotency&lt;&#x2F;code&gt; table &lt;em&gt;after&lt;&#x2F;em&gt; processing the request, just before returning the response to the caller. We are going to change that: we will insert a new row as soon as the handler is invoked.&lt;br &#x2F;&gt;
We don&#x27;t know the final response at that point - we haven&#x27;t started processing yet! We must relax the &lt;code&gt;NOT NULL&lt;&#x2F;code&gt; constraints on some of the columns:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate add relax_null_checks_on_idempotency
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;ALTER TABLE &lt;&#x2F;span&gt;&lt;span&gt;idempotency ALTER COLUMN response_status_code DROP NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;ALTER TABLE &lt;&#x2F;span&gt;&lt;span&gt;idempotency ALTER COLUMN response_body DROP NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;ALTER TABLE &lt;&#x2F;span&gt;&lt;span&gt;idempotency ALTER COLUMN response_headers DROP NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now insert a row as soon as the handler gets invoked using the information we have up to that point - the user id and the idempotency key, our composite primary key.&lt;&#x2F;p&gt;
&lt;p&gt;The first request will succeed in inserting a row into &lt;code&gt;idempotency&lt;&#x2F;code&gt;. The second request, instead, will fail due to our uniqueness constraint.&lt;br &#x2F;&gt;
That is not what we want:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;if the first request completed, we want to return the saved response;&lt;&#x2F;li&gt;
&lt;li&gt;if the first request is still ongoing, we want to &lt;strong&gt;wait&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The first scenario can be accommodated by using Postgres&#x27; &lt;code&gt;ON CONFLICT&lt;&#x2F;code&gt; statement - it allows us to define what should happen when an &lt;code&gt;INSERT&lt;&#x2F;code&gt; fails due to a constraint violation (e.g. uniqueness).&lt;br &#x2F;&gt;
We have two options: &lt;code&gt;ON CONFLICT DO NOTHING&lt;&#x2F;code&gt; and &lt;code&gt;ON CONFLICT DO UPDATE&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
&lt;code&gt;ON CONFLICT DO NOTHING&lt;&#x2F;code&gt;, as you might guess, does nothing - it simply swallows the error. We can detect that the row was already there by checking the number of rows that were affected by the statement.&lt;br &#x2F;&gt;
&lt;code&gt;ON CONFLICT DO UPDATE&lt;&#x2F;code&gt;, instead, can be used to modify the pre-existing row - e.g. &lt;code&gt;ON CONFLICT DO UPDATE SET updated_at = now()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We will use &lt;code&gt;ON CONFLICT DO NOTHING&lt;&#x2F;code&gt; - if no new row was inserted, we will try to fetch the saved response.&lt;br &#x2F;&gt;
Before we start implementing, there is an issue we need to solve: our code no longer compiles. Our code has not been updated to deal with the fact that a few columns in &lt;code&gt;idempotency&lt;&#x2F;code&gt; are now nullable.
We must update the query to ask &lt;code&gt;sqlx&lt;&#x2F;code&gt; to forcefully assume that the columns will not be null - if we are wrong, it will cause an error at runtime.&lt;br &#x2F;&gt;
The syntax is similar to the type casting syntax we used previously to deal with header pairs - we must append a &lt;code&gt;!&lt;&#x2F;code&gt; to the column alias name:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_saved_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SELECT 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_status_code as &amp;quot;response_status_code!&amp;quot;, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_headers as &amp;quot;response_headers!: Vec&amp;lt;HeaderPairRecord&amp;gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_body as &amp;quot;response_body!&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        user_id,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s now define the skeleton of a new function, the one we will invoke at the beginning of our request handler - &lt;code&gt;try_processing&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
It will try to perform the insertion we just discussed - if it fails because a row already exists, we will assume that a response has been saved and try to return it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;mod.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub use &lt;&#x2F;span&gt;&lt;span&gt;persistence::{try_processing, NextAction};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;NextAction {
&lt;&#x2F;span&gt;&lt;span&gt;    StartProcessing,
&lt;&#x2F;span&gt;&lt;span&gt;    ReturnSavedResponse(HttpResponse)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;IdempotencyKey, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;NextAction, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    todo!()
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Our handler will invoke &lt;code&gt;try_processing&lt;&#x2F;code&gt; instead of &lt;code&gt;get_saved_response&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::idempotency::{try_processing, NextAction};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; idempotency_key: IdempotencyKey = idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e400)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;idempotency_key, *user_id)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        NextAction::StartProcessing =&amp;gt; {}
&lt;&#x2F;span&gt;&lt;span&gt;        NextAction::ReturnSavedResponse(saved_response) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;success_message&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Ok(saved_response);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;success_message&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;see_other&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&#x2F;newsletters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;idempotency_key, *user_id, response)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;success_message&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; FlashMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    FlashMessage::info(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;The newsletter issue has been published!&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now flesh out &lt;code&gt;try_processing&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;IdempotencyKey,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;NextAction, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; n_inserted_rows = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        INSERT INTO idempotency (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            user_id, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            idempotency_key,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            created_at
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        ) 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        VALUES ($1, $2, now()) 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        ON CONFLICT DO NOTHING
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        user_id,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(pool)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;rows_affected&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; n_inserted_rows &amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(NextAction::StartProcessing)
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; saved_response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_saved_response&lt;&#x2F;span&gt;&lt;span&gt;(pool, idempotency_key, user_id)
&lt;&#x2F;span&gt;&lt;span&gt;            .await?
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;ok_or_else&lt;&#x2F;span&gt;&lt;span&gt;(|| 
&lt;&#x2F;span&gt;&lt;span&gt;                anyhow::anyhow!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;We expected a saved response, we didn&amp;#39;t find it&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;            )?;
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(NextAction::ReturnSavedResponse(saved_response))
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A bunch of our tests will start failing. What is going on?&lt;br &#x2F;&gt;
Log inspection highlights a &lt;code&gt;duplicate key value violates unique constraint &quot;idempotency_pkey&quot;&lt;&#x2F;code&gt;. Guess what? We forgot to update &lt;code&gt;save_response&lt;&#x2F;code&gt;! It&#x27;s trying to insert &lt;em&gt;another&lt;&#x2F;em&gt; row into &lt;code&gt;idempotency&lt;&#x2F;code&gt; for the same combination of user id and idempotency key - it needs to perform an &lt;code&gt;UPDATE&lt;&#x2F;code&gt; instead of an &lt;code&gt;INSERT&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query_unchecked!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        UPDATE idempotency
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SET 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_status_code = $3, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_headers = $4,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            response_body = $5
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        WHERE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            user_id = $1 AND
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            idempotency_key = $2
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        user_id,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        status_code,
&lt;&#x2F;span&gt;&lt;span&gt;        headers,
&lt;&#x2F;span&gt;&lt;span&gt;        body.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We are back to square one - &lt;code&gt;concurrent_form_submission_is_handled_gracefully&lt;&#x2F;code&gt; is the only failing test. What have we gained?&lt;br &#x2F;&gt;
Very little - the second request returns an error instead of sending emails out twice. An improvement, but not yet where we want to land.&lt;&#x2F;p&gt;
&lt;p&gt;We need to find a way to cause the &lt;code&gt;INSERT&lt;&#x2F;code&gt; in &lt;code&gt;try_processing&lt;&#x2F;code&gt; to wait instead of erroring out when a retry arrives before the first request has completed processing.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;9-2-1-transaction-isolation-levels&quot;&gt;9.2.1. Transaction Isolation Levels&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s do an experiment: we will wrap the &lt;code&gt;INSERT&lt;&#x2F;code&gt; in &lt;code&gt;try_processing&lt;&#x2F;code&gt; and the &lt;code&gt;UPDATE&lt;&#x2F;code&gt; in &lt;code&gt;save_response&lt;&#x2F;code&gt; in a single SQL transaction.&lt;br &#x2F;&gt;
What do you think it&#x27;s going to happen?&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;sqlx::{Postgres, Transaction};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;allow&lt;&#x2F;span&gt;&lt;span&gt;(clippy::large_enum_variant)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;NextAction {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Return transaction for later usage
&lt;&#x2F;span&gt;&lt;span&gt;    StartProcessing(Transaction&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;, Postgres&amp;gt;),
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; transaction = pool.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;begin&lt;&#x2F;span&gt;&lt;span&gt;().await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; n_inserted_rows = sqlx::query!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction)
&lt;&#x2F;span&gt;&lt;span&gt;        .await?
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;rows_affected&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; n_inserted_rows &amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(NextAction::StartProcessing(transaction))
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; No longer a `Pool`!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;transaction&lt;&#x2F;span&gt;&lt;span&gt;: Transaction&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;, Postgres&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query_unchecked!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction)
&lt;&#x2F;span&gt;&lt;span&gt;        .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    transaction.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span&gt;().await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; transaction = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;idempotency_key, *user_id)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        NextAction::StartProcessing(t) =&amp;gt; t,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(transaction, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All our tests are passing! But &lt;strong&gt;why&lt;&#x2F;strong&gt;?&lt;br &#x2F;&gt;
It boils down to locks and transaction isolation levels!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;READ COMMITTED&lt;&#x2F;code&gt; is the default isolation level in Postgres. We have not tuned this setting, therefore this is the case for the queries in our application as well.&lt;br &#x2F;&gt;
Postgres&#x27; documentation describes the behaviour at this isolation level as follows:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;[...] a SELECT query (without a FOR UPDATE&#x2F;SHARE clause) sees only data committed before the query began; it never sees either uncommitted data or changes committed during query execution by concurrent transactions. In effect, a SELECT query sees a snapshot of the database as of the instant the query begins to run.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Data-altering statements, instead, will be influenced by uncommitted transactions that are trying to alter the same set of rows:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;UPDATE&lt;&#x2F;code&gt;, &lt;code&gt;DELETE&lt;&#x2F;code&gt;, &lt;code&gt;SELECT FOR UPDATE&lt;&#x2F;code&gt; [...] will only find target rows that were committed as of the command start time. However, such a target row might have already been updated (or deleted or locked) by another concurrent transaction by the time it is found. In this case, &lt;strong&gt;the would-be updater will wait for the first updating transaction to commit or roll back (if it is still in progress)&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This is exactly what is happening in our case.&lt;br &#x2F;&gt;
The &lt;code&gt;INSERT&lt;&#x2F;code&gt; statement fired by the second request must wait for outcome of the SQL transaction started by the first request.&lt;br &#x2F;&gt;
If the latter commits, the former will &lt;code&gt;DO NOTHING&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
If the latter rolls back, the former will actually perform the insertion.&lt;&#x2F;p&gt;
&lt;p&gt;It is worth highlighting that this strategy will &lt;strong&gt;not&lt;&#x2F;strong&gt; work if using stricter isolation levels.&lt;br &#x2F;&gt;
We can test this pretty easily:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;idempotency&#x2F;persistence.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; transaction = pool.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;begin&lt;&#x2F;span&gt;&lt;span&gt;().await?;
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;SET TRANSACTION ISOLATION LEVEL repeatable read&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction)
&lt;&#x2F;span&gt;&lt;span&gt;        .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; n_inserted_rows = sqlx::query!(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The second concurrent request will fail due to a database error: &lt;code&gt;could not serialize access due to concurrent update&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
&lt;code&gt;repeatable read&lt;&#x2F;code&gt; is designed to prevent non-repeatable reads (who would have guessed?): the same &lt;code&gt;SELECT&lt;&#x2F;code&gt; query, if run twice in a row within the same transaction, should return the same data.&lt;br &#x2F;&gt;
This has consequences for statements such as &lt;code&gt;UPDATE&lt;&#x2F;code&gt;: if they are executed within a &lt;code&gt;repeatable read&lt;&#x2F;code&gt; transaction, they cannot modify or lock rows changed by other transactions after the repeatable read transaction began.&lt;br &#x2F;&gt;
This is why the transaction initiated by the second request fails to commit in our little experiment above. The same would have happened if we had chosen &lt;code&gt;serializable&lt;&#x2F;code&gt;, the strictest isolation level available in Postgres.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;10-dealing-with-errors&quot;&gt;10. Dealing With Errors&lt;&#x2F;h1&gt;
&lt;p&gt;We made some solid progress - our implementation handles duplicated requests gracefully, no matter if they arrive concurrently or sequentially.&lt;br &#x2F;&gt;
What about errors?&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s add another test case:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-toml &quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;#! Cargo.toml
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# [...]
&lt;&#x2F;span&gt;&lt;span&gt;[dev-dependencies]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;serde_urlencoded &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0.7.1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;# [...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;newsletter.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;fake::faker::internet::en::SafeEmail;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;fake::faker::name::en::Name;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;fake::Fake;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;wiremock::MockBuilder;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Short-hand for a common mocking setup
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;when_sending_an_email&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; MockBuilder {
&lt;&#x2F;span&gt;&lt;span&gt;    Mock::given(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;email&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;method&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;transient_errors_do_not_cause_duplicate_deliveries_on_retries&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Arrange
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; app = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn_app&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_request_body = serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Newsletter title&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Newsletter body as plain text&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;Newsletter body as HTML&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;idempotency_key&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: uuid::Uuid::new_v4().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;to_string&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Two subscribers instead of one!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;create_confirmed_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;create_confirmed_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;    app.test_user.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;login&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app).await;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Part 1 - Submit newsletter form
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Email delivery fails for the second subscriber
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_sending_an_email&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;respond_with&lt;&#x2F;span&gt;&lt;span&gt;(ResponseTemplate::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;up_to_n_times&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;mount&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app.email_server)
&lt;&#x2F;span&gt;&lt;span&gt;        .await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_sending_an_email&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;respond_with&lt;&#x2F;span&gt;&lt;span&gt;(ResponseTemplate::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;500&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;up_to_n_times&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;mount&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app.email_server)
&lt;&#x2F;span&gt;&lt;span&gt;        .await;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;post_publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;newsletter_request_body).await;
&lt;&#x2F;span&gt;&lt;span&gt;    assert_eq!(response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_u16&lt;&#x2F;span&gt;&lt;span&gt;(), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;500&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Part 2 - Retry submitting the form
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Email delivery will succeed for both subscribers now
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;when_sending_an_email&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;respond_with&lt;&#x2F;span&gt;&lt;span&gt;(ResponseTemplate::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;named&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Delivery retry&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;mount&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;app.email_server)
&lt;&#x2F;span&gt;&lt;span&gt;        .await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;post_publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;newsletter_request_body).await;
&lt;&#x2F;span&gt;&lt;span&gt;    assert_eq!(response.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;as_u16&lt;&#x2F;span&gt;&lt;span&gt;(), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;303&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we did not send out duplicates
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;create_unconfirmed_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;TestApp) -&amp;gt; ConfirmationLinks {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We are working with multiple subscribers now,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; their details must be randomised to avoid conflicts!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; name: String = Name().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fake&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; email: String = SafeEmail().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fake&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; body = serde_urlencoded::to_string(&amp;amp;serde_json::json!({
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: name,
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: email
&lt;&#x2F;span&gt;&lt;span&gt;    }))
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The test does not pass - we are seeing yet another instance of duplicated delivery:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;thread &lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;newsletter::transient_errors_do_not_cause_duplicate_deliveries_on_retries&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;panicked&lt;&#x2F;span&gt;&lt;span&gt; at &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Verifications failed:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;- Delivery retry.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        Expected range of matching incoming requests: == 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        Number of matched incoming requests: 2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It makes sense, if you think again about our idempotency implementation: the SQL transaction inserting into the &lt;code&gt;idempotency&lt;&#x2F;code&gt; table commits exclusively when processing succeeds.&lt;br &#x2F;&gt;
Errors lead to an early return - this triggers a rollback when the &lt;code&gt;Transaction&amp;lt;&#x27;static, Postgres&amp;gt;&lt;&#x2F;code&gt; value is dropped.&lt;&#x2F;p&gt;
&lt;p&gt;Can we do better?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;10-1-distributed-transactions&quot;&gt;10.1. Distributed Transactions&lt;&#x2F;h2&gt;
&lt;p&gt;The pain we are feeling is a common issue in real-world applications - you lose transactionality&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2PC&quot;&gt;15&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; when executing logic that touches, at the same time, your local state and a remote state managed by another system&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#microservices&quot;&gt;16&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I happen to be fascinated by the technical challenges in distributed systems, but I am well aware that users do not share my passion. They want to get something done, they do not care about the internals - and rightly so.&lt;br &#x2F;&gt;
A newsletter author expects one of the following scenarios after clicking on &lt;code&gt;Submit&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the issue was delivered to all subscribers;&lt;&#x2F;li&gt;
&lt;li&gt;the issue could not be published, therefore nobody received it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Our implementation allows for a third scenario at the moment: the issue could not be published (&lt;code&gt;500 Internal Server Error&lt;&#x2F;code&gt;), but &lt;em&gt;some&lt;&#x2F;em&gt; subscribers received it anyway.&lt;br &#x2F;&gt;
That won&#x27;t do - &lt;strong&gt;partial&lt;&#x2F;strong&gt; execution is not acceptable, the system must end up in a sensible state.&lt;br &#x2F;&gt;
There are two common approaches to solve this problem: &lt;strong&gt;backward recovery&lt;&#x2F;strong&gt; and &lt;strong&gt;forward recovery&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;10-2-backward-recovery&quot;&gt;10.2. Backward Recovery&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Backward recovery&lt;&#x2F;strong&gt; tries to achieve a semantic rollback by executing &lt;strong&gt;compensating actions&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Let&#x27;s imagine we are working on an e-commerce checkout system: we have already charged the customer for the products in their basket but, when trying to authorize the shipment, we discover that one of the items is now out of stock.&lt;br &#x2F;&gt;
We can perform a backward recovery by cancelling all shipment instructions and refunding the customer for the entire amount of their basket.&lt;br &#x2F;&gt;
The recovery mechanism is not transparent to the customer - they will still see two payments on their transaction history, the original charge and the following refund. We are also likely to send out an email to explain what happened. But their balance, the &lt;em&gt;state&lt;&#x2F;em&gt; they care about, has been restored.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;10-3-forward-recovery&quot;&gt;10.3. Forward Recovery&lt;&#x2F;h2&gt;
&lt;p&gt;Backward recovery is not a good fit for our newsletter delivery system - we cannot &quot;unsend&quot; an email nor would it make sense to send a follow-up email asking subscribers to ignore the email we sent before (it&#x27;d be funny though).&lt;&#x2F;p&gt;
&lt;p&gt;We must try to perform &lt;strong&gt;forward recovery&lt;&#x2F;strong&gt; - drive the overall workflow to completion even if one or more sub-tasks did not succeed.&lt;br &#x2F;&gt;
We have two options: &lt;strong&gt;active&lt;&#x2F;strong&gt; and &lt;strong&gt;passive&lt;&#x2F;strong&gt; recovery.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Passive recovery&lt;&#x2F;strong&gt; pushes on the API caller the responsibility to drive the workflow to completion.&lt;br &#x2F;&gt;
The request handler leverages &lt;em&gt;checkpoints&lt;&#x2F;em&gt; to keep track of its progress - e.g. &quot;123 emails have been sent out&quot;. If the handler crashes, the next API call will resume processing from the latest checkpoint, minimizing the amount of duplicated work (if any). After enough retries, the workflow will eventually complete.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Active recovery&lt;&#x2F;strong&gt;, instead, does not require the caller to do anything apart from kicking off the workflow. The system must self-heal.&lt;br &#x2F;&gt;
We would rely on a background process - e.g. a background task on our API - to detect newsletter issues whose delivery stopped halfway. The process would then drive the delivery to completion.&lt;br &#x2F;&gt;
Healing would happen &lt;strong&gt;asynchronously&lt;&#x2F;strong&gt; - outside the lifecycle of the original &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; request.&lt;&#x2F;p&gt;
&lt;p&gt;Passive recovery makes for a poor user experience - the newsletter author has to submit the form over and over again until they receive a success response back.
The author is in an awkward position - is the error they are seeing related to a transient issue encountered during delivery? Or is it the database failing when trying to fetch the list of subscribers? In other words, will retries actually lead to a success, eventually?&lt;br &#x2F;&gt;
If they choose to give up retrying, while in the middle of delivery, the system is once again left in an inconsistent state.&lt;&#x2F;p&gt;
&lt;p&gt;We will therefore opt for active recovery in our implementation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;10-4-asynchronous-processing&quot;&gt;10.4. Asynchronous Processing&lt;&#x2F;h2&gt;
&lt;p&gt;Active recovery has its rough edges as well.&lt;br &#x2F;&gt;
We do not want the author to receive an error back from the API while, under the hood, newsletter delivery has been kicked off.&lt;&#x2F;p&gt;
&lt;p&gt;We can improve the user experience by changing the expectations for &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
A successful form submission currently implies that the new newsletter issue has been validated and delivered to all subscribers.&lt;br &#x2F;&gt;
We can reduce its scope: a successful form submission will mean that the newsletter has been validated and &lt;strong&gt;will&lt;&#x2F;strong&gt; be delivered to all subscribers, &lt;strong&gt;asynchronously&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, a successful form submission guarantees to the author that the delivery workflow has been correctly kicked off. They just need to wait for all emails to go out, but they have nothing to worry about - it will happen&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#dashboard&quot;&gt;17&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The request handler of &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; is no longer going to dispatch emails - it will simply enqueue a list of tasks that will be fulfilled asynchronously by a set of background workers. We will use another Postgres table as our task queue - it will be named &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;At a glance, it might look like a small difference - we are just shifting around when work needs to happen. But it has a powerful implication: we recover transactionality.&lt;br &#x2F;&gt;
Our subscribers&#x27; data, our idempotency records, the task queue - they all live in Postgres. All the operations performed by &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; can be wrapped in a single SQL transaction - either they all succeed, or nothing happened.&lt;br &#x2F;&gt;
The caller no longer needs to second guess the response of our API or try to reason about its implementation!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-4-1-newsletter-issues&quot;&gt;10.4.1. &lt;code&gt;newsletter_issues&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;By dispatching eagerly, we never needed to store the details of the issues we were sending out. To pursue our new strategy, this has to change: we will start persisting newsletter issues in a dedicated &lt;code&gt;newsletter_issues&lt;&#x2F;code&gt; table.&lt;br &#x2F;&gt;
The schema should not come as a surprise:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate add create_newsletter_issues_table
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;-- migrations&#x2F;20220211080603_create_newsletter_issues_table.sql
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;CREATE TABLE &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletter_issues&lt;&#x2F;span&gt;&lt;span&gt; (
&lt;&#x2F;span&gt;&lt;span&gt;   newsletter_issue_id uuid NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   title &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   text_content &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   html_content &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   published_at &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;(newsletter_issue_id)
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s write a matching &lt;code&gt;insert_newsletter_issue&lt;&#x2F;code&gt; function - we&#x27;ll need it soon:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;sqlx::{Postgres, Transaction};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;uuid::Uuid;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(skip_all)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;insert_newsletter_issue&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;transaction&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span&gt;Transaction&amp;lt;&amp;#39;_, Postgres&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Uuid, sqlx::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; newsletter_issue_id = Uuid::new_v4();
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        INSERT INTO newsletter_issues (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            newsletter_issue_id, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            title, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            text_content, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            html_content,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            published_at
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        VALUES ($1, $2, $3, $4, now())
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        newsletter_issue_id,
&lt;&#x2F;span&gt;&lt;span&gt;        title,
&lt;&#x2F;span&gt;&lt;span&gt;        text_content,
&lt;&#x2F;span&gt;&lt;span&gt;        html_content 
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(transaction)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(newsletter_issue_id)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;10-4-2-issue-delivery-queue&quot;&gt;10.4.2. &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;When it comes to tasks, we are going to keep it simple:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate add create_issue_delivery_queue_table
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;-- migrations&#x2F;20220211080603_create_issue_delivery_queue_table.sql
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;CREATE TABLE &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;issue_delivery_queue&lt;&#x2F;span&gt;&lt;span&gt; (
&lt;&#x2F;span&gt;&lt;span&gt;   newsletter_issue_id uuid NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;REFERENCES&lt;&#x2F;span&gt;&lt;span&gt; newsletter_issues (newsletter_issue_id),
&lt;&#x2F;span&gt;&lt;span&gt;   subscriber_email &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;TEXT &lt;&#x2F;span&gt;&lt;span&gt;NOT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NULL&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;PRIMARY KEY&lt;&#x2F;span&gt;&lt;span&gt;(newsletter_issue_id, subscriber_email)
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sqlx&lt;&#x2F;span&gt;&lt;span&gt; migrate run
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can create the task set using a single insert query:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(skip_all)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;enqueue_delivery_tasks&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;transaction&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span&gt;Transaction&amp;lt;&amp;#39;_, Postgres&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;newsletter_issue_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), sqlx::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        INSERT INTO issue_delivery_queue (
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            newsletter_issue_id, 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            subscriber_email
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SELECT $1, email
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        FROM subscriptions
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        WHERE status = &amp;#39;confirmed&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        newsletter_issue_id,
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(transaction)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;10-4-3-post-admin-newsletters&quot;&gt;10.4.3. &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;We are ready to overhaul our request handler by putting together the pieces we just built:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;routes&#x2F;admin&#x2F;newsletter&#x2F;post.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    name = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Publish a newsletter issue&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    skip_all,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fields&lt;&#x2F;span&gt;&lt;span&gt;(user_id=%&amp;amp;*user_id)
&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;publish_newsletter&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span&gt;: web::Form&amp;lt;FormData&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: web::Data&amp;lt;PgPool&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;user_id&lt;&#x2F;span&gt;&lt;span&gt;: web::ReqData&amp;lt;UserId&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;HttpResponse, actix_web::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; user_id = user_id.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into_inner&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; FormData {
&lt;&#x2F;span&gt;&lt;span&gt;        title,
&lt;&#x2F;span&gt;&lt;span&gt;        text_content,
&lt;&#x2F;span&gt;&lt;span&gt;        html_content,
&lt;&#x2F;span&gt;&lt;span&gt;        idempotency_key,
&lt;&#x2F;span&gt;&lt;span&gt;    } = form.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; idempotency_key: IdempotencyKey = idempotency_key.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e400)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; transaction = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_processing&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;idempotency_key, *user_id)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        NextAction::StartProcessing(t) =&amp;gt; t,
&lt;&#x2F;span&gt;&lt;span&gt;        NextAction::ReturnSavedResponse(saved_response) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;success_message&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Ok(saved_response);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; issue_id = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;insert_newsletter_issue&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction, &amp;amp;title, &amp;amp;text_content, &amp;amp;html_content)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;context&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Failed to store newsletter issue details&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;enqueue_delivery_tasks&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction, issue_id)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;context&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Failed to enqueue delivery tasks&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;see_other&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;admin&#x2F;newsletters&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; response = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;save_response&lt;&#x2F;span&gt;&lt;span&gt;(transaction, &amp;amp;idempotency_key, *user_id, response)
&lt;&#x2F;span&gt;&lt;span&gt;        .await
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span&gt;(e500)?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;success_message&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(response)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;success_message&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; FlashMessage {
&lt;&#x2F;span&gt;&lt;span&gt;    FlashMessage::info(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;The newsletter issue has been accepted - \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        emails will go out shortly.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can also delete &lt;code&gt;get_confirmed_subscribers&lt;&#x2F;code&gt; and &lt;code&gt;ConfirmedSubscriber&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The logic in the request handler is now quite linear. The author is also going to have a quicker feedback loop - the endpoint no longer has to iterate over hundreds of subscribers before redirecting them to a success page.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-4-4-email-processing&quot;&gt;10.4.4. Email Processing&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s move our focus to the delivery instead.&lt;br &#x2F;&gt;
We need to consume tasks from &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt;. There are going to be multiple delivery workers running at the same time - at least one per API instance.&lt;br &#x2F;&gt;
A naive approach would get us into trouble:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; (newsletter_issue_id, subscriber_email)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; issue_delivery_queue
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;LIMIT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Multiple workers would pick the same task and we would end up with a lot of duplicated emails.&lt;&#x2F;p&gt;
&lt;p&gt;We need synchronization. Once again, we are going to leverage the database - we will use &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;explicit-locking.html#LOCKING-ROWS&quot;&gt;row-level locks&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Postgres 9.5 introduced the &lt;code&gt;SKIP LOCKED&lt;&#x2F;code&gt; clause - it allows &lt;code&gt;SELECT&lt;&#x2F;code&gt; statements to ignore all rows that are currently locked by another concurrent operation.&lt;br &#x2F;&gt;
&lt;code&gt;FOR UPDATE&lt;&#x2F;code&gt;, instead, can be used to lock the rows returned by a &lt;code&gt;SELECT&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
We are going to combine them:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sql&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;SELECT&lt;&#x2F;span&gt;&lt;span&gt; (newsletter_issue_id, subscriber_email)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; issue_delivery_queue
&lt;&#x2F;span&gt;&lt;span&gt;FOR &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;UPDATE
&lt;&#x2F;span&gt;&lt;span&gt;SKIP LOCKED
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;LIMIT &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives us a concurrency-safe queue.&lt;br &#x2F;&gt;
Each worker is going to select an uncontested task (&lt;code&gt;SKIP LOCKED&lt;&#x2F;code&gt; and &lt;code&gt;LIMIT 1&lt;&#x2F;code&gt;); the task itself is going to become unavailable to other workers (&lt;code&gt;FOR UPDATE&lt;&#x2F;code&gt;) for the duration of the over-arching SQL transaction.&lt;br &#x2F;&gt;
When the task is complete (i.e. the email has been sent), we are going to delete the corresponding row from &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt; and commit our changes.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s code it up:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! lib.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub mod &lt;&#x2F;span&gt;&lt;span&gt;issue_delivery_worker;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::email_client::EmailClient;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;sqlx::{PgPool, Postgres, Transaction};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;tracing::{field::display, Span};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;uuid::Uuid;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    skip_all,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;fields&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        newsletter_issue_id=tracing::field::Empty,
&lt;&#x2F;span&gt;&lt;span&gt;        subscriber_email=tracing::field::Empty
&lt;&#x2F;span&gt;&lt;span&gt;    ),
&lt;&#x2F;span&gt;&lt;span&gt;    err
&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email_client&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;EmailClient
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;Some((transaction, issue_id, email)) = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dequeue_task&lt;&#x2F;span&gt;&lt;span&gt;(pool).await? {
&lt;&#x2F;span&gt;&lt;span&gt;        Span::current()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;newsletter_issue_id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;display&lt;&#x2F;span&gt;&lt;span&gt;(issue_id))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;subscriber_email&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;display&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;email));
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; TODO: send email
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;delete_task&lt;&#x2F;span&gt;&lt;span&gt;(transaction, issue_id, &amp;amp;email).await?;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;type &lt;&#x2F;span&gt;&lt;span&gt;PgTransaction = Transaction&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static&lt;&#x2F;span&gt;&lt;span&gt;, Postgres&amp;gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(skip_all)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;dequeue_task&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Option&amp;lt;(PgTransaction, Uuid, String)&amp;gt;, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; transaction = pool.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;begin&lt;&#x2F;span&gt;&lt;span&gt;().await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; r = sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SELECT newsletter_issue_id, subscriber_email
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        FROM issue_delivery_queue
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        FOR UPDATE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SKIP LOCKED
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        LIMIT 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fetch_optional&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;Some(r) = r {
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(Some((
&lt;&#x2F;span&gt;&lt;span&gt;            transaction,
&lt;&#x2F;span&gt;&lt;span&gt;            r.newsletter_issue_id,
&lt;&#x2F;span&gt;&lt;span&gt;            r.subscriber_email,
&lt;&#x2F;span&gt;&lt;span&gt;        )))
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(None)
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(skip_all)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;delete_task&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;transaction&lt;&#x2F;span&gt;&lt;span&gt;: PgTransaction,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;issue_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    sqlx::query!(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        DELETE FROM issue_delivery_queue
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        WHERE 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            newsletter_issue_id = $1 AND
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            subscriber_email = $2 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        issue_id,
&lt;&#x2F;span&gt;&lt;span&gt;        email
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;execute&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; transaction)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    transaction.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span&gt;().await?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To actually send the email, we need to fetch the newsletter content first:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;NewsletterIssue {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;text_content&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;html_content&lt;&#x2F;span&gt;&lt;span&gt;: String,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(skip_all)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_issue&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;issue_id&lt;&#x2F;span&gt;&lt;span&gt;: Uuid
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;NewsletterIssue, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; issue = sqlx::query_as!(
&lt;&#x2F;span&gt;&lt;span&gt;        NewsletterIssue,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;r&lt;&#x2F;span&gt;&lt;span&gt;#&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        SELECT title, text_content, html_content
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        FROM newsletter_issues
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        WHERE
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;            newsletter_issue_id = $1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;#,
&lt;&#x2F;span&gt;&lt;span&gt;        issue_id
&lt;&#x2F;span&gt;&lt;span&gt;    )
&lt;&#x2F;span&gt;&lt;span&gt;    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fetch_one&lt;&#x2F;span&gt;&lt;span&gt;(pool)
&lt;&#x2F;span&gt;&lt;span&gt;    .await?;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(issue)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can then recover the dispatch logic that used to live in &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::domain::SubscriberEmail;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;PgPool, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email_client&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;EmailClient
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;Some((transaction, issue_id, email)) = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dequeue_task&lt;&#x2F;span&gt;&lt;span&gt;(pool).await? {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span&gt;SubscriberEmail::parse(email.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;()) {
&lt;&#x2F;span&gt;&lt;span&gt;            Ok(email) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; issue = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_issue&lt;&#x2F;span&gt;&lt;span&gt;(pool, issue_id).await?;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;Err(e) = email_client
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;send_email&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;                        &amp;amp;email,
&lt;&#x2F;span&gt;&lt;span&gt;                        &amp;amp;issue.title,
&lt;&#x2F;span&gt;&lt;span&gt;                        &amp;amp;issue.html_content,
&lt;&#x2F;span&gt;&lt;span&gt;                        &amp;amp;issue.text_content,
&lt;&#x2F;span&gt;&lt;span&gt;                    )
&lt;&#x2F;span&gt;&lt;span&gt;                    .await
&lt;&#x2F;span&gt;&lt;span&gt;                {
&lt;&#x2F;span&gt;&lt;span&gt;                    tracing::error!(
&lt;&#x2F;span&gt;&lt;span&gt;                        error.cause_chain = ?e,
&lt;&#x2F;span&gt;&lt;span&gt;                        error.message = %e,
&lt;&#x2F;span&gt;&lt;span&gt;                        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Failed to deliver issue to a confirmed subscriber. \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;                         Skipping.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;                    );
&lt;&#x2F;span&gt;&lt;span&gt;                }
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            Err(e) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                tracing::error!(
&lt;&#x2F;span&gt;&lt;span&gt;                    error.cause_chain = ?e,
&lt;&#x2F;span&gt;&lt;span&gt;                    error.message = %e,
&lt;&#x2F;span&gt;&lt;span&gt;                    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Skipping a confirmed subscriber. \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;                     Their stored contact details are invalid&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;                );
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;delete_task&lt;&#x2F;span&gt;&lt;span&gt;(transaction, issue_id, &amp;amp;email).await?;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As you can see, we do not retry when the delivery attempt fails due to a Postmark error.&lt;br &#x2F;&gt;
This could be changed by enhancing &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt; - e.g. adding a &lt;code&gt;n_retries&lt;&#x2F;code&gt; and &lt;code&gt;execute_after&lt;&#x2F;code&gt; columns to keep track of how many attempts have already taken place and how long we should wait before trying again. Try implementing it as an exercise!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-4-5-worker-loop&quot;&gt;10.4.5. Worker loop&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;try_execute_task&lt;&#x2F;code&gt; tries to deliver a single email - we need a background task that keeps pulling from &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt; and fulfills tasks as they become available.&lt;&#x2F;p&gt;
&lt;p&gt;We can use an infinite loop:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::time::Duration;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;worker_loop&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;pool&lt;&#x2F;span&gt;&lt;span&gt;: PgPool, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email_client&lt;&#x2F;span&gt;&lt;span&gt;: EmailClient
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;loop &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;email_client).await.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_err&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;            tokio::time::sleep(Duration::from_secs(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)).await;
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we experience a transient failure&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#invalid-emails&quot;&gt;18&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, we need to sleep for a while to improve our future chances of success. This could be further refined by introducing an &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;blogs&#x2F;architecture&#x2F;exponential-backoff-and-jitter&#x2F;&quot;&gt;exponential backoff with jitter&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;There is another scenario we need to keep in mind, apart from failure: &lt;code&gt;issue_delivery_queue&lt;&#x2F;code&gt; might be empty.&lt;br &#x2F;&gt;
When that is the case, &lt;code&gt;try_execute_task&lt;&#x2F;code&gt; is going to be invoked &lt;em&gt;continuously&lt;&#x2F;em&gt;. That translates into an avalanche of unnecessary queries to the database.&lt;br &#x2F;&gt;
We can mitigate this risk by changing the signature of &lt;code&gt;try_execute_task&lt;&#x2F;code&gt; - we need to know if it actually managed to dequeue something.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;enum &lt;&#x2F;span&gt;&lt;span&gt;ExecutionOutcome {
&lt;&#x2F;span&gt;&lt;span&gt;    TaskCompleted,
&lt;&#x2F;span&gt;&lt;span&gt;    EmptyQueue,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;ExecutionOutcome, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; task = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dequeue_task&lt;&#x2F;span&gt;&lt;span&gt;(pool).await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; task.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;is_none&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Ok(ExecutionOutcome::EmptyQueue);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let &lt;&#x2F;span&gt;&lt;span&gt;(transaction, issue_id, email) = task.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(ExecutionOutcome::TaskCompleted)
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;worker_loop&lt;&#x2F;code&gt; can now become smarter:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;worker_loop&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;loop &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;pool, &amp;amp;email_client).await {
&lt;&#x2F;span&gt;&lt;span&gt;            Ok(ExecutionOutcome::EmptyQueue) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                tokio::time::sleep(Duration::from_secs(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;)).await;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;             Err(_) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;                tokio::time::sleep(Duration::from_secs(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)).await;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            Ok(ExecutionOutcome::TaskCompleted) =&amp;gt; {}
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No more busy looping, yay!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-4-6-launching-background-workers&quot;&gt;10.4.6. Launching Background Workers&lt;&#x2F;h3&gt;
&lt;p&gt;We have a worker loop - but it is not launched anywhere.&lt;br &#x2F;&gt;
Let&#x27;s start by building the required dependencies based on the configuration values&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#to-share-or-not-to-share&quot;&gt;19&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::{configuration::Settings, startup::get_connection_pool};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;run_worker_until_stopped&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;configuration&lt;&#x2F;span&gt;&lt;span&gt;: Settings
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; connection_pool = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_connection_pool&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;configuration.database);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; sender_email = configuration
&lt;&#x2F;span&gt;&lt;span&gt;        .email_client
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;sender&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Invalid sender email address.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; timeout = configuration.email_client.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; email_client = EmailClient::new(
&lt;&#x2F;span&gt;&lt;span&gt;        configuration.email_client.base_url,
&lt;&#x2F;span&gt;&lt;span&gt;        sender_email,
&lt;&#x2F;span&gt;&lt;span&gt;        configuration.email_client.authorization_token,
&lt;&#x2F;span&gt;&lt;span&gt;        timeout,
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;worker_loop&lt;&#x2F;span&gt;&lt;span&gt;(connection_pool, email_client).await
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To run our background worker and the API side-to-side we need to restructure our &lt;code&gt;main&lt;&#x2F;code&gt; function.&lt;br &#x2F;&gt;
We are going to build the &lt;code&gt;Future&lt;&#x2F;code&gt; for each of the two long-running tasks - &lt;code&gt;Future&lt;&#x2F;code&gt;s are lazy in Rust, so nothing happens until they are actually awaited.&lt;br &#x2F;&gt;
We will use &lt;code&gt;tokio::select!&lt;&#x2F;code&gt; to get both tasks to make progress concurrently. &lt;code&gt;tokio::select!&lt;&#x2F;code&gt; returns as soon as one of the two tasks completes or errors out:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;zero2prod::issue_delivery_worker::run_worker_until_stopped;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; subscriber = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;zero2prod&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;(), &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;(), std::io::stdout);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;init_subscriber&lt;&#x2F;span&gt;&lt;span&gt;(subscriber);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; configuration = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_configuration&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Failed to read configuration.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; application = Application::build(configuration.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;        .await?
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;run_until_stopped&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; worker = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;run_worker_until_stopped&lt;&#x2F;span&gt;&lt;span&gt;(configuration);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    tokio::select! {
&lt;&#x2F;span&gt;&lt;span&gt;        _ = application =&amp;gt; {},
&lt;&#x2F;span&gt;&lt;span&gt;        _ = worker =&amp;gt; {},
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There is a pitfall to be mindful of when using &lt;code&gt;tokio::select!&lt;&#x2F;code&gt; - all selected &lt;code&gt;Future&lt;&#x2F;code&gt;s are polled as a single task. This has consequences, as &lt;code&gt;tokio&lt;&#x2F;code&gt;&#x27;s documentation highlights:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;By running all async expressions on the current task, the expressions are able to run concurrently but not in parallel. This means all expressions are run on the same thread and if one branch blocks the thread, all other expressions will be unable to continue. If parallelism is required, spawn each async expression using &lt;code&gt;tokio::spawn&lt;&#x2F;code&gt; and pass the join handle to &lt;code&gt;select!&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We should definitely follow their recommendation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; application = Application::build(configuration.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;()).await?;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; application_task = tokio::spawn(application.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;run_until_stopped&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; worker_task = tokio::spawn(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;run_worker_until_stopped&lt;&#x2F;span&gt;&lt;span&gt;(configuration));
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    tokio::select! {
&lt;&#x2F;span&gt;&lt;span&gt;        _ = application_task =&amp;gt; {},
&lt;&#x2F;span&gt;&lt;span&gt;        _ = worker_task =&amp;gt; {},
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As it stands, we have no visibility into which task completed first or if they completed successfully at all. Let&#x27;s add some logging:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;std::fmt::{Debug, Display};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;tokio::task::JoinError;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    tokio::select! {
&lt;&#x2F;span&gt;&lt;span&gt;        o = application_task =&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;report_exit&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;API&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, o),
&lt;&#x2F;span&gt;&lt;span&gt;        o = worker_task =&amp;gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;report_exit&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Background worker&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, o),
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    Ok(())
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;report_exit&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;task_name&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;, 
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;outcome&lt;&#x2F;span&gt;&lt;span&gt;: Result&amp;lt;Result&amp;lt;(), impl Debug + Display&amp;gt;, JoinError&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; outcome {
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(Ok(())) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;            tracing::info!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{} has exited&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, task_name)
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        Ok(Err(e)) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;            tracing::error!(
&lt;&#x2F;span&gt;&lt;span&gt;                error.cause_chain = ?e, 
&lt;&#x2F;span&gt;&lt;span&gt;                error.message = %e, 
&lt;&#x2F;span&gt;&lt;span&gt;                &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{} failed&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, 
&lt;&#x2F;span&gt;&lt;span&gt;                task_name
&lt;&#x2F;span&gt;&lt;span&gt;            )
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        Err(e) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;            tracing::error!(
&lt;&#x2F;span&gt;&lt;span&gt;                error.cause_chain = ?e, 
&lt;&#x2F;span&gt;&lt;span&gt;                error.message = %e, 
&lt;&#x2F;span&gt;&lt;span&gt;                &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;{}&amp;#39; task failed to complete&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, 
&lt;&#x2F;span&gt;&lt;span&gt;                task_name
&lt;&#x2F;span&gt;&lt;span&gt;            )
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It is looking pretty solid!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-4-7-updating-the-test-suite&quot;&gt;10.4.7. Updating The Test Suite&lt;&#x2F;h3&gt;
&lt;p&gt;We have one little problem left - many of our tests are failing. They were written when emails were being delivered synchronously, which is no longer the case.
How should we deal with them?&lt;&#x2F;p&gt;
&lt;p&gt;Launching a background worker would mimic the behaviour of our application, but it would make for a fragile test suite - we would have to sleep for arbitrary time intervals waiting for the background worker to process the email tasks we just enqueued. We are bound to have flaky tests sooner or later.&lt;br &#x2F;&gt;
An alternative approach relies on launching the worker on demand, asking it to consume all available tasks. It deviates slightly from the behaviour in our &lt;code&gt;main&lt;&#x2F;code&gt; function, but it manages to exercise most of the code while being significantly more robust. This is what we are going for!&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s add an &lt;code&gt;EmailClient&lt;&#x2F;code&gt; instance to our &lt;code&gt;TestApp&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;configuration.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use crate&lt;&#x2F;span&gt;&lt;span&gt;::email_client::EmailClient;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;EmailClientSettings {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; EmailClient {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; sender_email = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;sender&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Invalid sender email address.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; timeout = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;timeout&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        EmailClient::new(
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.base_url,
&lt;&#x2F;span&gt;&lt;span&gt;            sender_email,
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.authorization_token,
&lt;&#x2F;span&gt;&lt;span&gt;            timeout,
&lt;&#x2F;span&gt;&lt;span&gt;        )
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;helpers.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;zero2prod::email_client::EmailClient;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;TestApp {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;email_client&lt;&#x2F;span&gt;&lt;span&gt;: EmailClient
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;spawn_app&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; TestApp {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; test_app = TestApp {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;        email_client: configuration.email_client.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;run_worker_until_stopped&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;configuration&lt;&#x2F;span&gt;&lt;span&gt;: Settings
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;(), anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; connection_pool = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_connection_pool&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;configuration.database);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Use helper function!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; email_client = configuration.email_client.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;worker_loop&lt;&#x2F;span&gt;&lt;span&gt;(connection_pool, email_client).await
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;startup.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;Application {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;configuration&lt;&#x2F;span&gt;&lt;span&gt;: Settings) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span&gt;, anyhow::Error&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; connection_pool = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_connection_pool&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;configuration.database);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Use helper function!
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; email_client = configuration.email_client.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can then write a helper to consume all enqueued tasks:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;helpers.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;zero2prod::issue_delivery_worker::{try_execute_task, ExecutionOutcome};
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;impl &lt;&#x2F;span&gt;&lt;span&gt;TestApp {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;dispatch_all_pending_emails&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;loop &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if let &lt;&#x2F;span&gt;&lt;span&gt;ExecutionOutcome::EmptyQueue =
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.db_pool, &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.email_client)
&lt;&#x2F;span&gt;&lt;span&gt;                    .await
&lt;&#x2F;span&gt;&lt;span&gt;                    .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            {
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;break&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! src&#x2F;issue_delivery_worker.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...] 
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mark as pub
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span&gt;ExecutionOutcome {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tracing&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;instrument&lt;&#x2F;span&gt;&lt;span&gt;(&#x2F;* *&#x2F;)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mark as pub
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt; async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;try_execute_task&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* *&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can update all the impacted test cases:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;! tests&#x2F;api&#x2F;newsletter.rs
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletters_are_not_delivered_to_unconfirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    assert!(html_page.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;i&amp;gt;The newsletter issue has been accepted - \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        emails will go out shortly.&amp;lt;&#x2F;i&amp;gt;&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    ));
&lt;&#x2F;span&gt;&lt;span&gt;    app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dispatch_all_pending_emails&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we haven&amp;#39;t sent the newsletter email
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletters_are_delivered_to_confirmed_subscribers&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    assert!(html_page.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;i&amp;gt;The newsletter issue has been accepted - \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        emails will go out shortly.&amp;lt;&#x2F;i&amp;gt;&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    ));
&lt;&#x2F;span&gt;&lt;span&gt;    app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dispatch_all_pending_emails&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we have sent the newsletter email
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;newsletter_creation_is_idempotent&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Part 2 - Follow the redirect
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; html_page = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_publish_newsletter_html&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    assert!(html_page.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;i&amp;gt;The newsletter issue has been accepted - \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        emails will go out shortly.&amp;lt;&#x2F;i&amp;gt;&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    ));
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Act - Part 4 - Follow the redirect
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; html_page = app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;get_publish_newsletter_html&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    assert!(html_page.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;contains&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;i&amp;gt;The newsletter issue has been accepted - \
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;        emails will go out shortly.&amp;lt;&#x2F;i&amp;gt;&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;    ));
&lt;&#x2F;span&gt;&lt;span&gt;    app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dispatch_all_pending_emails&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we have sent the newsletter email **once**
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tokio&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;]
&lt;&#x2F;span&gt;&lt;span&gt;async &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;concurrent_form_submission_is_handled_gracefully&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; [...]
&lt;&#x2F;span&gt;&lt;span&gt;    app.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;dispatch_all_pending_emails&lt;&#x2F;span&gt;&lt;span&gt;().await;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Mock verifies on Drop that we have sent the newsletter email **once**
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; We deleted `transient_errors_do_not_cause_duplicate_deliveries_on_retries`
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; It is no longer relevant given the redesign
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The tests are passing, we made it!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Well, we &lt;em&gt;almost&lt;&#x2F;em&gt; made it.&lt;br &#x2F;&gt;
We neglected one detail: there is no expiry mechanism for our idempotency keys. Try designing one as an exercise, using what we learned on background workers as a reference.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;11-epilogue&quot;&gt;11. Epilogue&lt;&#x2F;h1&gt;
&lt;p&gt;This is where our journey together comes to an end.&lt;&#x2F;p&gt;
&lt;p&gt;We started from an empty skeleton. Look at our project now: fully functional, well tested, reasonably secure - a proper minimum viable product. The project was never the goal though - it was an excuse, an opportunity to see what it feels like to write a production-ready API using Rust.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Zero To Production In Rust&lt;&#x2F;em&gt; started with a question, a question I hear every other day:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can Rust be a productive language for API development?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I have taken you on a tour. I showed you a little corner of the Rust ecosystem, an opinionated yet powerful toolkit. I tried to explain, to the best of my abilities, the key language features.&lt;br &#x2F;&gt;
The choice is now yours: you have learned enough to keep walking on your own, if you wish to do so.&lt;&#x2F;p&gt;
&lt;p&gt;Rust&#x27;s adoption in the industry is taking off: we are living through an inflection point. It was my ambition to write a book that could serve as a ticket for this rising tide - an onboarding guide for those who want to be a part of this story.&lt;br &#x2F;&gt;
This is just the beginning - the future of this community is yet to be written, but it is looking bright.&lt;&#x2F;p&gt;

&lt;p class=&quot;article_signature&quot;&gt;
    By &lt;a href=&quot;&#x2F;&quot;&gt;Luca Palmieri&lt;&#x2F;a&gt; &lt;span aria-hidden=&quot;true&quot;&gt;·&lt;&#x2F;span&gt; &lt;time datetime=&quot;2022-03-14T08:08:10.47Z&quot;&gt;March 2022&lt;&#x2F;time&gt;
&lt;&#x2F;p&gt;

&lt;nav class=&quot;prev-and-next&quot;&gt;
    &lt;div class=&quot;nav_row&quot;&gt;
        
        &lt;div class=&quot;prev&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Previously&lt;&#x2F;p&gt;&lt;a href=&quot;&amp;#x2F;posts&amp;#x2F;session-based-authentication-in-rust&amp;#x2F;&quot;&gt;Session-based Authentication&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
        
        &lt;div class=&quot;next&quot;&gt;
            &lt;p class=&quot;hint&quot;&gt;Next up&lt;&#x2F;p&gt;&lt;a href=&quot;&#x2F;subscribe&quot;&gt;Coming Soon&lt;&#x2F;a&gt;
        &lt;&#x2F;div&gt;
        
    &lt;&#x2F;div&gt;
    &lt;div class=&quot;buy&quot;&gt;
        &lt;a href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Buy The Book&lt;&#x2F;a&gt;
    &lt;&#x2F;div&gt;
&lt;&#x2F;nav&gt;
&lt;hr &#x2F;&gt;
&lt;blockquote&gt;
&lt;p&gt;This article is a sample from &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;&lt;strong&gt;Zero To Production In Rust&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, a hands-on introduction to backend development in Rust.&lt;br &#x2F;&gt;
You can get a copy of the book at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;zero2prod.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;12-footnotes&quot;&gt;12. Footnotes&lt;&#x2F;h1&gt;
&lt;details open&gt;
   &lt;summary&gt;Click to expand!&lt;&#x2F;summary&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;take-home&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;At the end of chapter 10 you were asked to convert &lt;code&gt;POST &#x2F;newsletters&lt;&#x2F;code&gt; (JSON + &#x27;Basic&#x27; auth) into &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; (HTML Form data + session-based auth) as a take-home exercise. Your implementation might differ slightly from mine, therefore the code blocks here might not match exactly what you see in your IDE. Check the book&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;zero-to-production&#x2F;tree&#x2F;root-chapter-10-part3&quot;&gt;GitHub repository&lt;&#x2F;a&gt; to compare solutions.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;form-bad-request&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;It is up for debate if this is actually the best way to handle an invalid body. Assuming no mistakes were made on our side, submitting the HTML form we serve on &lt;code&gt;GET &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; should always result into a request body that passes the basic validation done by the &lt;code&gt;Json&lt;&#x2F;code&gt; extractor - a.k.a. we get all the fields we expect. But mistakes are a possibility - we cannot rule out that some of the types used in &lt;code&gt;FormData&lt;&#x2F;code&gt; as fields might start doing more advanced validation in the future - it&#x27;d be safer to redirect the user back to the form page with a proper error message when body validation fails. You can try it out as an exercise.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;postmark-batch&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;Postmark provides a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;postmarkapp.com&#x2F;developer&#x2F;user-guide&#x2F;send-email-with-api&#x2F;batch-emails&quot;&gt;batch email API&lt;&#x2F;a&gt; - it is not clear, from their documentation, if they retry messages within a batch to ensure best-effort delivery. Regardless, there is a maximum batch size (500) - if your audience is big enough you have to think about how to batch batches: back to square zero. From a learning perspective, we can safely ignore their batch API entirely.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;disable-button&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;Client-side JavaScript can be used to disable buttons after they have been clicked, reducing the likelihood of this scenario.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;payments&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;Real-world payment systems would most likely return a &lt;code&gt;202 Accepted&lt;&#x2F;code&gt; - payment authorization, execution and settlement happen at different points in time. We are keeping things simple for the sake of our example.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;beneficiaries&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;6&lt;&#x2F;sup&gt;
&lt;p&gt;The other accounts on the payment network are not exposed to us via the API (i.e. we can&#x27;t query for John&#x27;s payment history), but we can still observe if one of our payments did or did not reach its beneficiary - e.g. by calling them or if they reach out to us to complain they haven&#x27;t received the money yet! API calls can have a material effect on the physical world around us - that&#x27;s why this whole computer thing is so powerful and scary at the same time!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;no-spec-idempotency-key&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;7&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;draft-ietf-httpapi-idempotency-key-header&#x2F;&quot;&gt;An &lt;code&gt;Internet-Draft&lt;&#x2F;code&gt; in the &lt;code&gt;httpapi&lt;&#x2F;code&gt; IETF working group&lt;&#x2F;a&gt; proposed to standardize the &lt;code&gt;Idempotency-Key&lt;&#x2F;code&gt; header, but the conversation does not seem to have moved forward (the draft expired in January 2022).&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fingerprinting&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;8&lt;&#x2F;sup&gt;
&lt;p&gt;We are not actually going to implement a likeness check for incoming requests - it can get quite tricky: do the headers matter? All of them? A subset of them? Does the body need to match byte-by-byte? Is it enough if it&#x27;s semantically equivalent (e.g. two JSON objects with identical keys and values)? It can be done, but it is beyond the scope of this chapter.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;dos-409&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;9&lt;&#x2F;sup&gt;
&lt;p&gt;If we are being pessimistic, this could be abused to mount a denial of service attack against the API. It can be avoided by enforcing fair-usage limitations - e.g. it&#x27;s OK to have a handful of concurrent requests for the same idempotency key, but the server will start returning errors if we end up dealing with tens of duplicates.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;fingerprint-generation&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;10&lt;&#x2F;sup&gt;
&lt;p&gt;Two different newsletter issues should not generate the same subscriber-specific idempotency key. If that were to happen, you wouldn&#x27;t be able to send two different issues one after the other because Postmark&#x27;s idempotency logic would prevent the second set of emails from going out. This is why we must include a fingerprint of the incoming request content in the generation logic for the subscriber-specific idempotency key - it ensures a unique outcome for each subscriber-newsletter issue pair. Alternatively, we must implement a likeness check to ensure that the same idempotency key cannot be used for two different requests to &lt;code&gt;POST &#x2F;admin&#x2F;newsletters&lt;&#x2F;code&gt; - i.e. the idempotency key is enough to ensure that the newsletter content is not the same.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;non-repeatable-read&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;11&lt;&#x2F;sup&gt;
&lt;p&gt;This is equivalent to a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Isolation_(database_systems)#Non-repeatable_reads&quot;&gt;non-repeatable read&lt;&#x2F;a&gt; in a relational database.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;in-memory-or-streaming&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;12&lt;&#x2F;sup&gt;
&lt;p&gt;We technically have another option: stream the response body directly to the database and then stream it back from the database directly to the caller.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;bytes-vec&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;13&lt;&#x2F;sup&gt;
&lt;p&gt;You can think of &lt;code&gt;Bytes&lt;&#x2F;code&gt; as a &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;&#x2F;code&gt; with extra perks - check out the documentation of the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bytes&#x2F;1.1.0&#x2F;bytes&#x2F;index.html&quot;&gt;&lt;code&gt;bytes&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; crate for more details.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;truncation-array-type&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;14&lt;&#x2F;sup&gt;
&lt;p&gt;If the type name ends up being too long, some truncation takes place as well.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2PC&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;15&lt;&#x2F;sup&gt;
&lt;p&gt;Protocols like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;martinfowler.com&#x2F;articles&#x2F;patterns-of-distributed-systems&#x2F;two-phase-commit.html&quot;&gt;2-phase commit&lt;&#x2F;a&gt; would allow us to have an all-or-nothing semantic in a distributed system, but they are not widely supported due to their complexity and drawbacks.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;microservices&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;16&lt;&#x2F;sup&gt;
&lt;p&gt;More often than not, the other system lives within your organization - it&#x27;s just a different microservice, with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;microservices.io&#x2F;patterns&#x2F;data&#x2F;database-per-service.html&quot;&gt;its own isolated data store&lt;&#x2F;a&gt;. You have traded the inner complexity of the monolith for the complexity of orchestrating changes across multiple sub-system - &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ferd.ca&#x2F;complexity-has-to-live-somewhere.html&quot;&gt;complexity has to live somewhere&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;dashboard&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;17&lt;&#x2F;sup&gt;
&lt;p&gt;The author would still benefit from having visibility into the delivery process - e.g. a page to track how many emails are still outstanding for a certain newsletter issue. Workflow observability is out of scope for the book, but it might be an interesting exercise to pursue on your own.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;invalid-emails&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;18&lt;&#x2F;sup&gt;
&lt;p&gt;Almost all errors returned by &lt;code&gt;try_execute_task&lt;&#x2F;code&gt; are transient in nature, except for invalid subscriber emails - sleeping is not going to fix those. Try refining the implementation to distinguish between transient and fatal failures, empowering &lt;code&gt;worker_loop&lt;&#x2F;code&gt; to react appropriately.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;to-share-or-not-to-share&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;19&lt;&#x2F;sup&gt;
&lt;p&gt;We are not re-using the dependencies we built for our &lt;code&gt;actix_web&lt;&#x2F;code&gt; application. This separation enables us, for example, to precisely control how many database connections are allocated to background tasks vs our API workloads. At the same time, this is clearly unnecessary at this stage: we could have built a single pool and HTTP client, passing &lt;code&gt;Arc&lt;&#x2F;code&gt; pointers to both sub-systems (API and worker). The right choice depends on the circumstances and the overall set of constraints.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;&#x2F;details&gt;
&lt;h1 id=&quot;book-table-of-contents&quot;&gt;Book - Table Of Contents&lt;&#x2F;h1&gt;
&lt;details&gt;
   &lt;summary&gt;Click to expand!&lt;&#x2F;summary&gt;
&lt;p&gt;&lt;em&gt;The Table of Contents is provisional and might change over time. The draft below is the most accurate picture at this point in time.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-05-24-zero-to-production-0-foreword&#x2F;&quot;&gt;Who Is This Book For&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-05-24-zero-to-production-0-foreword&#x2F;&quot;&gt;What Is This Book About&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-06-06-zero-to-production-1-setup-toolchain-ides-ci&#x2F;&quot;&gt;Getting Started&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;Installing The Rust Toolchain&lt;&#x2F;li&gt;
&lt;li&gt;Project Setup&lt;&#x2F;li&gt;
&lt;li&gt;IDEs&lt;&#x2F;li&gt;
&lt;li&gt;Continuous Integration&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-06-21-zero-to-production-2-learn-by-building-an-email-newsletter&#x2F;&quot;&gt;Our Driving Example&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;What Should Our Newsletter Do?&lt;&#x2F;li&gt;
&lt;li&gt;Working In Iterations&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Sign Up A New Subscriber
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-08-09-zero-to-production-3-how-to-bootstrap-a-new-rust-web-api-from-scratch&#x2F;&quot;&gt;Choosing A Web Framework&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-08-09-zero-to-production-3-how-to-bootstrap-a-new-rust-web-api-from-scratch&#x2F;&quot;&gt;Our First Endpoint: A Basic Health Check&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-08-09-zero-to-production-3-how-to-bootstrap-a-new-rust-web-api-from-scratch&#x2F;&quot;&gt;Our First Integration Test&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-08-31-zero-to-production-3-5-html-forms-databases-integration-tests&quot;&gt;Reading Request Data&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-08-31-zero-to-production-3-5-html-forms-databases-integration-tests&quot;&gt;Adding A Database&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-08-31-zero-to-production-3-5-html-forms-databases-integration-tests&quot;&gt;Persisting A New Subscriber&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-09-27-zero-to-production-4-are-we-observable-yet&quot;&gt;Telemetry&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;Unknown Unknowns&lt;&#x2F;li&gt;
&lt;li&gt;Observability&lt;&#x2F;li&gt;
&lt;li&gt;Logging&lt;&#x2F;li&gt;
&lt;li&gt;Instrumenting &#x2F;POST subscriptions&lt;&#x2F;li&gt;
&lt;li&gt;Structured Logging&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-11-01-zero-to-production-5-how-to-deploy-a-rust-application&quot;&gt;Go Live&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;We Must Talk About Deployments&lt;&#x2F;li&gt;
&lt;li&gt;Choosing Our Tools&lt;&#x2F;li&gt;
&lt;li&gt;A Dockerfile For Our Application&lt;&#x2F;li&gt;
&lt;li&gt;Deploy To DigitalOcean Apps Platform&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;2020-12-11-zero-to-production-6-domain-modelling&quot;&gt;Rejecting Invalid Subscribers #1&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;Requirements&lt;&#x2F;li&gt;
&lt;li&gt;First Implementation&lt;&#x2F;li&gt;
&lt;li&gt;Validation Is A Leaky Cauldron&lt;&#x2F;li&gt;
&lt;li&gt;Type-Driven Development&lt;&#x2F;li&gt;
&lt;li&gt;Ownership Meets Invariants&lt;&#x2F;li&gt;
&lt;li&gt;Panics&lt;&#x2F;li&gt;
&lt;li&gt;Error As Values - &lt;code&gt;Result&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Reject Invalid Subscribers #2
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;how-to-write-a-rest-client-in-rust-with-reqwest-and-wiremock&quot;&gt;Confirmation Emails&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;how-to-write-a-rest-client-in-rust-with-reqwest-and-wiremock&quot;&gt;&lt;code&gt;EmailClient&lt;&#x2F;code&gt;, Our Email Delivery Component&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;skeleton-and-principles-for-a-maintainable-test-suite&quot;&gt;Skeletons And Principles For A Maintainable Test Suite&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;zero-downtime-deployments&quot;&gt;Zero Downtime Deployments&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;zero-downtime-deployments&quot;&gt;Multi-step Database Migrations&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;zero-downtime-deployments&quot;&gt;Sending A Confirmation Email&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;zero-downtime-deployments&quot;&gt;Database Transactions&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;error-handling-rust&quot;&gt;Error Handling&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;What Is The Purpose Of Errors?&lt;&#x2F;li&gt;
&lt;li&gt;Error Reporting For Operators&lt;&#x2F;li&gt;
&lt;li&gt;Errors For Control Flow&lt;&#x2F;li&gt;
&lt;li&gt;Avoid &quot;Ball Of Mud&quot; Error Enums&lt;&#x2F;li&gt;
&lt;li&gt;Who Should Log Errors?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;naive-newsletter-delivery&quot;&gt;Naive Newsletter Delivery&lt;&#x2F;a&gt;
&lt;ul&gt;
&lt;li&gt;User Stories Are Not Set In Stone&lt;&#x2F;li&gt;
&lt;li&gt;Do Not Spam Unconfirmed Subscribers&lt;&#x2F;li&gt;
&lt;li&gt;All Confirmed Subscribers Receive New Issues&lt;&#x2F;li&gt;
&lt;li&gt;Implementation Strategy&lt;&#x2F;li&gt;
&lt;li&gt;Body Schema&lt;&#x2F;li&gt;
&lt;li&gt;Fetch Confirmed Subscribers List&lt;&#x2F;li&gt;
&lt;li&gt;Send Newsletter Emails&lt;&#x2F;li&gt;
&lt;li&gt;Validation Of Stored Data&lt;&#x2F;li&gt;
&lt;li&gt;Limitations Of The Naive Approach&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Securing Our API
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;password-authentication-in-rust&quot;&gt;Authentication&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;password-authentication-in-rust&quot;&gt;Password-based Authentication&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;password-authentication-in-rust&quot;&gt;Is it safe?&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;&#x2F;posts&#x2F;password-authentication-in-rust&quot;&gt;What Should We Do Next&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Fault-tolerant Newsletter Delivery&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</description>
      </item>
      <item>
          <title>Looking back at 2021</title>
          <pubDate>Sat, 08 Jan 2022 12:00:10 +0000</pubDate>
          <author>Unknown</author>
          <link>https://80a8f3c0.lpalmieri.pages.dev/posts/2021-year-in-review/</link>
          <guid>https://80a8f3c0.lpalmieri.pages.dev/posts/2021-year-in-review/</guid>
          <description xml:base="https://80a8f3c0.lpalmieri.pages.dev/posts/2021-year-in-review/">&lt;p&gt;Another year has passed - time for a yearly review!&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;image&#x2F;2021-in-review&#x2F;year_in_review.png&quot; width=&quot;80%&quot; &gt;
&lt;h2 id=&quot;index&quot;&gt;Index&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;2021-year-in-review&#x2F;#the-blog&quot;&gt;The blog&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;2021-year-in-review&#x2F;#the-book&quot;&gt;The book&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;2021-year-in-review&#x2F;#open-source&quot;&gt;Open source&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;2021-year-in-review&#x2F;#work&quot;&gt;Work&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;2021-year-in-review&#x2F;#life&quot;&gt;Life&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;80a8f3c0.lpalmieri.pages.dev&#x2F;posts&#x2F;2021-year-in-review&#x2F;#next-year&quot;&gt;Next year&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;the-blog&quot;&gt;The blog&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s start with the blog!&lt;br &#x2F;&gt;
2021 was the second year in a row of consistent writing - 8 articles in 12 months. It was not as profilic as 2020, when I managed to write 14 pieces, although the average length was higher this year (4540 words in 2020 vs 6610 words in 2021).&lt;&#x2F;p&gt;
&lt;p&gt;There was definitely less variety this year - all articles were part of &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; with the exception of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lpalmieri.com&#x2F;posts&#x2F;caching-types-in-a-microservice-architecture&#x2F;&quot;&gt;an essay on caching patterns in a microservice architecture&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
I tried hard to &lt;em&gt;keep the main thing the main thing&lt;&#x2F;em&gt;, as my CRO Max loves to say. Finishing &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; was my top priority and I tried to avoid side quests as much as possible. I&#x27;ll start digging through my various notes and drafts in 2022!&lt;&#x2F;p&gt;
&lt;p&gt;What about readers?&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;At the beginning of 2021 I switched all my websites to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;plausible.io&quot;&gt;Plausible.io&lt;&#x2F;a&gt;, a privacy-friendly alternative to Google Analytics.
As a bonus, the numbers for my blog are now &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;plausible.io&#x2F;lpalmieri.com&quot;&gt;publicly available&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;plausible.io&#x2F;lpalmieri.com?period=custom&amp;from=2021-01-01&amp;to=2021-12-31&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;&#x2F;image&#x2F;2021-in-review&#x2F;analytics_blog_2021.png&quot; width=&quot;100%&quot; &gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;2021 was another year of significant growth - 112k unique visitors! Almost 2 times more than 2020 (66k).
Almost all articles managed to &quot;break through&quot; when published - a significant improvement compared to 2020 when some of the posts were simply ignored on launch.&lt;&#x2F;p&gt;
&lt;p&gt;The top ten articles (in terms of visits) span both 2020 and 2021:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;plausible.io&#x2F;lpalmieri.com&#x2F;pages?period=custom&amp;from=2021-01-01&amp;to=2021-12-31&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;&#x2F;image&#x2F;2021-in-review&#x2F;top-10-pages-2021.png&quot; width=&quot;70%&quot; &gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;My &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lpalmieri.com&#x2F;posts&#x2F;2020-07-04-choosing-a-rust-web-framework-2020-edition&#x2F;&quot;&gt;review of Rust web frameworks keeps going strong&lt;&#x2F;a&gt;, although it is in dire need of an update. I have been repeating to myself&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;I&#x27;ll update it when &lt;code&gt;rocket&lt;&#x2F;code&gt; and &lt;code&gt;actix-web&lt;&#x2F;code&gt; go out of beta&#x2F;rc!&quot;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;but that took way longer than I initially estimated. Expect a 2022 edition in a couple months, no matter what happens in the ecosystem.&lt;&#x2F;p&gt;
&lt;p&gt;Looking at the new entries in the top ten, I am particularly proud of &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.lpalmieri.com&#x2F;posts&#x2F;error-handling-rust&#x2F;&quot;&gt;Error handling in Rust - A deep dive&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; and &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.lpalmieri.com&#x2F;posts&#x2F;password-authentication-in-rust&#x2F;&quot;&gt;Password authentication in Rust - from scratch&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; - my two favourite pieces among the ones I published in 2021.&lt;&#x2F;p&gt;
&lt;p&gt;Where is the traffic coming from?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;plausible.io&#x2F;lpalmieri.com&#x2F;sources?period=custom&amp;from=2021-01-01&amp;to=2021-12-31&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;&#x2F;image&#x2F;2021-in-review&#x2F;seo-traffic-2021.png&quot; width=&quot;100%&quot; &gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Organic traffic through Google accounts for roughly half of those 112k readers, especially when it comes to older articles. This year I spent some time doing basic SEO optimisation - it seems it paid off.&lt;br &#x2F;&gt;
The rest is a mix of referrals from social platforms (Twitter and Reddit, primarily), traffic from my newsletter and a few Rust-specific websites (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arewewebyet.org&quot;&gt;arewewebyet.org&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;this-week-in-rust.org&#x2F;&quot;&gt;This Week In Rust&lt;&#x2F;a&gt;, etc.).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-book&quot;&gt;The book&lt;&#x2F;h2&gt;
&lt;p&gt;In 2020 I started writing a book on backend development, &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;. The goal for 2021 was to finish it!&lt;br &#x2F;&gt;
Sadly, I didn&#x27;t succeed - I published the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lpalmieri.com&#x2F;posts&#x2F;session-based-authentication-in-rust&#x2F;&quot;&gt;last part of chapter 10&lt;&#x2F;a&gt; a couple of days ago, but there is one more chapter to write (plus the editing work required to release a deadtree version).&lt;&#x2F;p&gt;
&lt;p&gt;I had another yearly goal related to &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;: sell at least 1000 copies.&lt;br &#x2F;&gt;
Well, it went a lot better than I imagined:&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;image&#x2F;2021-in-review&#x2F;book-sales-2021.png&quot; width=&quot;100%&quot; &gt;
&lt;p&gt;More than 3000 sales!&lt;br &#x2F;&gt;
The book revenues are now comparable to my yearly salary - wild. I am also quite happy with the reach of the discount program - I managed to offer a free copy to ~100 students and unemployed readers, on top of location-based discounts.&lt;&#x2F;p&gt;
&lt;p&gt;I am looking forward to writing a retrospective on the entire project.&lt;br &#x2F;&gt;
I am particularly keen to dismantle the myth that &quot;you cannot make money on a tech book&quot; - it takes a lot of hard work, but it is possible. You need to be mindful of the traps along the way though - e.g. publishers who won&#x27;t pay you more than 15% of net book sales.&lt;br &#x2F;&gt;
Really curious to see how the book is going to perform, commercially, in 2022.&lt;&#x2F;p&gt;
&lt;p&gt;Something else happened in 2021 - I launched a readers&#x27; Discord server.&lt;br &#x2F;&gt;
I was a bit wary, at the beginning - community management is its own kind of challenge and it can easily consume tons of time. In hindsight, I was overly pessimistic - I am happy I jumped. We have had many interesting conversations on the server and it gave me the opportunity to have a closer look at the issues readers struggle with as they go through the book - the content improved signficantly as a result.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;open-source&quot;&gt;Open Source&lt;&#x2F;h2&gt;
&lt;p&gt;2021 followed the same ethos of 2020 when it comes to my personal Open Source projects - &lt;strong&gt;small and self-contained&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
My crates (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lukemathwalker&#x2F;cargo-chef&quot;&gt;&lt;code&gt;cargo-chef&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;wiremock&quot;&gt;&lt;code&gt;wiremock&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;tracing-actix-web&#x2F;&quot;&gt;&lt;code&gt;tracing-actix-web&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, etc.) did not require a lot of maintenance - just the occasional bug fix or small feature PR.&lt;&#x2F;p&gt;
&lt;p&gt;I ended up releasing a new crate as a side effect of working on &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; - &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LukeMathWalker&#x2F;actix-web-flash-messages&quot;&gt;&lt;code&gt;actix-web-flash-messages&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a port of Django&#x27;s flash message framework to &lt;code&gt;actix-web&lt;&#x2F;code&gt;. I have also started a significant rework of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;actix&#x2F;actix-extras&#x2F;pull&#x2F;212&quot;&gt;&lt;code&gt;actix-session&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, yet to be merged.&lt;&#x2F;p&gt;
&lt;p&gt;It is fair to say that I got more and more involved with the overall &lt;code&gt;actix-web&lt;&#x2F;code&gt; ecosystem as a by-product of &lt;em&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; - I am quite liking it so far! I expect to contribute more in 2022: not to the framework itself but more to its &quot;plugin&quot; modules (&lt;code&gt;actix-session&lt;&#x2F;code&gt;, &lt;code&gt;actix-identity&lt;&#x2F;code&gt;, etc.).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;work&quot;&gt;Work&lt;&#x2F;h2&gt;
&lt;p&gt;I finally had the opportunity to meet many of the colleagues who joined in the midst of the first and second waves of the pandemic. It was long overdue!&lt;&#x2F;p&gt;
&lt;p&gt;From a business perspective, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;truelayer.com&quot;&gt;TrueLayer&lt;&#x2F;a&gt; had an amazing 2021. The company is now valued at 1 billion dollars (🦄), product usage is skyrocketing and we have grown to be over 300 people - 15x more than when I joined!&lt;&#x2F;p&gt;
&lt;p&gt;Speaking for myself, I managed to put a name to the work I had been doing for a while - I was promoted to Principal Engineer at the beginning of the year!&lt;br &#x2F;&gt;
The product I helped to launch in 2020, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.truelayer.com&#x2F;#payouts-api-v1&quot;&gt;Payouts API&lt;&#x2F;a&gt;, has grown in scope and ambitions (Europe!) and it is now supported by tens of extremely capable engineers.&lt;br &#x2F;&gt;
This gave me the opportunity to shift my focus elsewhere in the latter part of the year - preparing for the Engineering organization for another year of crazy growth, both in terms of people and technology.&lt;br &#x2F;&gt;
Leadership hiring, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;truelayer.com&#x2F;blog&#x2F;how-we-evaluate-and-adopt-new-technology&quot;&gt;technology governance&lt;&#x2F;a&gt;, architecture, organization-wide initiatives - they have taken a much larger part of my day to day compared to last year.&lt;&#x2F;p&gt;
&lt;p&gt;That was clearly not enough though to keep me busy though - at the end of Q3 I decided to spin up a new team, Engineering Ops.&lt;br &#x2F;&gt;
The approach and goals are very similar to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gigamonkeys.com&#x2F;flowers&#x2F;&quot;&gt;Twitter&#x27;s Engineering Effectiveness group&lt;&#x2F;a&gt; - multiply the impact that every single engineer can have inside the organization. The team is still incubating, but it has already managed to make a dent on a few long-running issues (e.g. alerting noise&#x2F;targeting). The scope is intentionally broad - from operations to build tooling, whatever makes the biggest impact on the day to day life of TrueLayer&#x27;s engineers. If that sounds interesting, we are looking for an &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;apply.workable.com&#x2F;truelayer&#x2F;j&#x2F;1412CCC1E2&#x2F;&quot;&gt;Engineering Manager&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;My work plans for 2022 are intentionally vague. The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;charity.wtf&#x2F;2017&#x2F;05&#x2F;11&#x2F;the-engineer-manager-pendulum&#x2F;&quot;&gt;pendulum&lt;&#x2F;a&gt; might swing in the other direction. Or I might end up sinking my engineering teeth in a new area. Gotta keep it interesting!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;life&quot;&gt;Life&lt;&#x2F;h2&gt;
&lt;p&gt;Food, friends, some travel - I breathed a lot more in 2021 compared to the first year of the pandemic.&lt;&#x2F;p&gt;
&lt;p&gt;I also got married!&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;image&#x2F;2021-in-review&#x2F;wedding-2021.jpeg&quot; width=&quot;100%&quot; &gt;
&lt;p&gt;Organizing a wedding during a pandemic is... quite an experience. It all went well in the end and we had a magical night - I couldn&#x27;t be happier.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-year&quot;&gt;Next year&lt;&#x2F;h2&gt;
&lt;p&gt;How did I do compared to the goal I set &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.lpalmieri.com&#x2F;posts&#x2F;2020-12-31-year-in-review&#x2F;&quot;&gt;at the beginning of the year&lt;&#x2F;a&gt;?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;❌ Finish &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;✅ Sell 1000 copies;&lt;&#x2F;li&gt;
&lt;li&gt;✅ Build in public! Both when it comes to my blog&#x2F;book and my side projects;&lt;&#x2F;li&gt;
&lt;li&gt;❌ Launch a small SaaS as a side-project;&lt;&#x2F;li&gt;
&lt;li&gt;✅ Speak at a conference that is not Rust-related;&lt;&#x2F;li&gt;
&lt;li&gt;❌ Get one PR merged into the Rust compiler;&lt;&#x2F;li&gt;
&lt;li&gt;✅ Travel when it&#x27;ll be safe again.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I am still keen on all the ones I didn&#x27;t manage to achieve.&lt;br &#x2F;&gt;
In 2022 I am looking to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Finish &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zero2prod.com&quot;&gt;Zero To Production In Rust&lt;&#x2F;a&gt;, including a paperback version;&lt;&#x2F;li&gt;
&lt;li&gt;Launch a small SaaS as a side-project;&lt;&#x2F;li&gt;
&lt;li&gt;Get one PR merged into the Rust compiler;&lt;&#x2F;li&gt;
&lt;li&gt;Visit three new countries;&lt;&#x2F;li&gt;
&lt;li&gt;Diversify my content.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;See you next year!&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
