<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anatolii Shliakhto</title>
    <description>The latest articles on DEV Community by Anatolii Shliakhto (@__17fa74fdf).</description>
    <link>https://dev.to/__17fa74fdf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3931449%2F62726859-d809-4b5e-8705-6e26dbba3cf3.png</url>
      <title>DEV Community: Anatolii Shliakhto</title>
      <link>https://dev.to/__17fa74fdf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__17fa74fdf"/>
    <language>en</language>
    <item>
      <title>Errors as Infrastructure: Why the first crate in NEXUS wasn't networking.</title>
      <dc:creator>Anatolii Shliakhto</dc:creator>
      <pubDate>Fri, 15 May 2026 12:41:38 +0000</pubDate>
      <link>https://dev.to/__17fa74fdf/errors-as-infrastructure-why-the-first-crate-in-nexus-wasnt-networking-546m</link>
      <guid>https://dev.to/__17fa74fdf/errors-as-infrastructure-why-the-first-crate-in-nexus-wasnt-networking-546m</guid>
      <description>&lt;h2&gt;
  
  
  Designing a metadata-centric failure contract for distributed Rust environments.
&lt;/h2&gt;

&lt;p&gt;When people introduce a new Rust project, they usually begin with networking, storage, async orchestration, or protocol design.&lt;/p&gt;

&lt;p&gt;I didn’t.&lt;/p&gt;

&lt;p&gt;The first crate I put into &lt;strong&gt;&lt;a href="https://github.com/AnatoliiShliakhto/nexus" rel="noopener noreferrer"&gt;NEXUS&lt;/a&gt;&lt;/strong&gt; was an error-handling crate: &lt;a href="https://github.com/AnatoliiShliakhto/nexus/tree/dev/crates/error" rel="noopener noreferrer"&gt;&lt;code&gt;nx-error&lt;/code&gt;&lt;/a&gt; (and its companion &lt;a href="https://github.com/AnatoliiShliakhto/nexus/tree/dev/crates/error-macros" rel="noopener noreferrer"&gt;&lt;code&gt;nx-error-macros&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That choice was not aesthetic. It was architectural.&lt;/p&gt;

&lt;p&gt;NEXUS is built around service boundaries, typed contracts, and execution environments where failures need to be represented consistently across layers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ccvnv1vhslhjcchpj9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ccvnv1vhslhjcchpj9s.png" alt="NEXUS Error Architecture: Flow from Domain Error through nx-error Hub to API, Logs, Metrics and UX" width="718" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is the first in a series about the technical foundations of NEXUS. It explains why I built &lt;code&gt;nx-error&lt;/code&gt;, what problems it was designed to solve, and which trade-offs mattered most: typed metadata, context separation, predictable propagation, and WASM-conscious ergonomics.&lt;/p&gt;




&lt;h2&gt;
  
  
  A quick look at the API
&lt;/h2&gt;

&lt;p&gt;The surface API is intentionally small. The goal was to make domain errors easy to define, but also useful to downstream systems: HTTP layers, logs, metrics, dashboards, and operators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nx_error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[error]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;DatabaseError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Connection lost"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DB_CONN_LOST"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;ConnectionLost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;#[error(message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Entity not found"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DB_NOT_FOUND"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;#[error(message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Database IO failure"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;507&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DB_IO_ERROR"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;source&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;std::io::Error)]&lt;/span&gt;
    &lt;span class="n"&gt;Io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error can then be used in ordinary Rust code without custom mapping glue at every callsite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;read_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DatabaseError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;DatabaseError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when an error needs operational context, it can be enriched where that context actually exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;read_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DatabaseError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"config.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to load configuration"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Check whether config.json exists in the application root"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the level of ergonomics I wanted: define domain semantics once, preserve source context automatically, and add diagnostic detail only where it becomes meaningful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why existing crates were not enough for this project
&lt;/h2&gt;

&lt;p&gt;Rust already has excellent tools for error handling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;thiserror&lt;/code&gt; is an excellent fit for typed library errors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;anyhow&lt;/code&gt; is excellent for application-level aggregation and rapid iteration.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;miette&lt;/code&gt; is great for diagnostics-heavy CLI workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;nx-error&lt;/code&gt; is not an attempt to replace them universally. It exists because NEXUS had a narrower and more demanding set of constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Public-safe and operator-grade output needed to be different things
&lt;/h3&gt;

&lt;p&gt;The same failure should not be serialized identically for every audience.&lt;/p&gt;

&lt;p&gt;An external client usually needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stable error code&lt;/li&gt;
&lt;li&gt;a concise message&lt;/li&gt;
&lt;li&gt;a status value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An operator or log pipeline needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the full source chain&lt;/li&gt;
&lt;li&gt;contextual details&lt;/li&gt;
&lt;li&gt;remediation hints&lt;/li&gt;
&lt;li&gt;enough structure for indexing and correlation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation had to be a first-class design goal, not something improvised later in an HTTP handler.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Error transport cost mattered
&lt;/h3&gt;

&lt;p&gt;In Rust, the size of an enum is dictated by its largest variant. That is fine until error variants begin carrying a lot of inline data: strings, identifiers, nested structures, wrapped sources, and ad hoc context.&lt;/p&gt;

&lt;p&gt;In ordinary service code this is often acceptable. In more constrained execution environments, especially WASM-oriented component boundaries, oversized error payloads become less attractive. They make error values heavier to move and blur the line between domain semantics and incidental diagnostics.&lt;/p&gt;

&lt;p&gt;I wanted a design where rich diagnostics did not automatically imply bloated variant layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Semantic metadata needed to survive propagation
&lt;/h3&gt;

&lt;p&gt;In a layered system, lower-level code often already knows important semantics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this is a &lt;code&gt;404&lt;/code&gt;, not a &lt;code&gt;500&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;this is a configuration error, not a business-rule violation&lt;/li&gt;
&lt;li&gt;this is a retryable infrastructure failure, not a client error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did not want every service layer to restate those semantics manually in handwritten mappers. That approach is repetitive and, more importantly, a source of drift.&lt;/p&gt;

&lt;p&gt;What I wanted was a way to define error meaning once and let it propagate predictably.&lt;/p&gt;




&lt;h2&gt;
  
  
  The core idea: errors as metadata-bearing contracts
&lt;/h2&gt;

&lt;p&gt;The key design decision in &lt;code&gt;nx-error&lt;/code&gt; was to treat errors not just as values implementing &lt;code&gt;std::error::Error&lt;/code&gt;, but as &lt;strong&gt;structured metadata carriers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That metadata is useful to multiple consumers at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Rust type system&lt;/li&gt;
&lt;li&gt;HTTP response mapping&lt;/li&gt;
&lt;li&gt;frontend i18n/error handling&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;telemetry sinks&lt;/li&gt;
&lt;li&gt;support and operations workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At a high level, the design revolves around a metadata model with concepts like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status&lt;/li&gt;
&lt;li&gt;machine-readable code&lt;/li&gt;
&lt;li&gt;message&lt;/li&gt;
&lt;li&gt;optional details&lt;/li&gt;
&lt;li&gt;optional help/remediation&lt;/li&gt;
&lt;li&gt;source chaining&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That may sound familiar conceptually, but the important part is how it changes engineering behavior. Once those fields become part of the error contract, developers stop treating errors as opaque strings and start treating them as typed operational events.&lt;/p&gt;

&lt;p&gt;That shift was one of the main goals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: "fat enums" don’t scale well as a system-wide contract
&lt;/h2&gt;

&lt;p&gt;A common pattern in Rust is to attach context directly to enum variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ServiceError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;NotFound&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;DatabaseFailure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is easy to write and often reasonable locally. But there is a structural downside: the enum’s size is determined by its largest variant. As more inline context accumulates, the error type gets heavier everywhere, even where most of that payload is irrelevant.&lt;/p&gt;

&lt;p&gt;That becomes less attractive when the error type is part of a project-wide contract rather than a private implementation detail.&lt;/p&gt;

&lt;p&gt;The direction I took in &lt;code&gt;nx-error&lt;/code&gt; was to keep the surface declaration concise while avoiding a design where every variant becomes a large inline payload carrier. In practice, that means treating rich diagnostic context as attached metadata rather than requiring each variant to own an ever-growing set of fields directly.&lt;/p&gt;

&lt;p&gt;The goal was not to make errors "tiny at all costs." It was more specific: preserve strong typing at the enum level without forcing every layer to pay for maximal inline context layout.&lt;/p&gt;

&lt;p&gt;That trade-off matters more in a platform crate than in an application-specific binary, because the error type becomes part of the shared vocabulary of every other crate that depends on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 2: boilerplate destroys consistency long before it destroys productivity
&lt;/h2&gt;

&lt;p&gt;One of the easiest ways to lose control of an error model is not through bad abstractions, but through small repetitive decisions spread across many modules.&lt;/p&gt;

&lt;p&gt;Without some shared conventions, teams tend to do all the following by hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invent error codes ad hoc&lt;/li&gt;
&lt;li&gt;write slightly different messages for the same failure class&lt;/li&gt;
&lt;li&gt;map similar failures to different statuses&lt;/li&gt;
&lt;li&gt;forget to preserve source chains&lt;/li&gt;
&lt;li&gt;attach context inconsistently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That kind of drift does not look dramatic in code review, but it becomes painfully visible in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dashboards become noisy&lt;/li&gt;
&lt;li&gt;metrics dimensions fragment&lt;/li&gt;
&lt;li&gt;logs become harder to search&lt;/li&gt;
&lt;li&gt;client behavior becomes inconsistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;nx-error&lt;/code&gt; leans heavily on &lt;strong&gt;convention over configuration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the variant name is already meaningful, the macro can infer useful defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UserNotFound&lt;/code&gt; → code &lt;code&gt;USER_NOT_FOUND&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UserNotFound&lt;/code&gt; → message &lt;code&gt;"User not found"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;unspecified failures can default into a sane internal status class&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not merely syntactic sugar. It is a mechanism for reducing semantic drift.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nx_error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[error]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;GatewayError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;UserNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;InternalFailure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not just shorter syntax. It is keeping declarations aligned with the operational vocabulary of the system.&lt;/p&gt;

&lt;p&gt;A good platform error crate should make the correct thing easier than the inconsistent thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 3: conversion across layers is usually repetitive and lossy
&lt;/h2&gt;

&lt;p&gt;In layered Rust systems, propagation only stays ergonomic if &lt;code&gt;?&lt;/code&gt; can rely on existing conversions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;db_call&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is manageable once. It becomes friction when every subsystem wraps another subsystem and every layer wants to preserve source information while exposing a stable top-level contract.&lt;/p&gt;

&lt;p&gt;The deeper problem is not typing the conversion. The deeper problem is &lt;strong&gt;semantic loss&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a lower-level error already carries meaningful metadata, forcing every layer to manually restate it is both repetitive and error-prone.&lt;/p&gt;

&lt;p&gt;That is where the propagation model in &lt;code&gt;nx-error&lt;/code&gt; mattered most.&lt;/p&gt;

&lt;p&gt;A domain-level error can define a source type directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[error]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;AppError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Configuration error"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CONFIG_ERROR"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;InvalidConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;#[error(&lt;/span&gt;
        &lt;span class="nd"&gt;message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Database operation failed"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DATABASE_ERROR"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;source&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;surrealdb::Error,&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;#[error(message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Migration failed"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MIGRATION_ERROR"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;#[error(message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"An internal system error occurred"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"INTERNAL_ERROR"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Internal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for integration-heavy cases, a single domain error can represent multiple lower-level technical failures through a shared upstream source model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[error]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ServiceError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[transparent(&lt;/span&gt;
        &lt;span class="nd"&gt;source&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;nx_http::error::Error,&lt;/span&gt;
        &lt;span class="nd"&gt;from&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;
            &lt;span class="nd"&gt;nx_http::url::ParseError,&lt;/span&gt;
            &lt;span class="nd"&gt;nx_http::request::RequestError,&lt;/span&gt;
        &lt;span class="nd"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This preserves an important invariant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Conversion should preserve intent, not erase it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If lower-level code already knows that something is "not found", "invalid", or "temporarily unavailable", the upper layers should not need to rediscover that by pattern-matching on strings or collapsing everything into &lt;code&gt;500 Internal Server Error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That becomes especially important once retries, API responses, dashboards, and alerting pipelines all depend on those distinctions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 4: the system needed two views of the same failure
&lt;/h2&gt;

&lt;p&gt;This was probably the most important requirement in NEXUS.&lt;/p&gt;

&lt;p&gt;The client-facing representation of an error should be stable and safe. The operator-facing representation should be rich and explanatory.&lt;/p&gt;

&lt;p&gt;Those are different outputs for different audiences.&lt;/p&gt;

&lt;p&gt;A client-safe representation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_SERVICE_CONFIGURATION_INVALID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Configuration variable contains an invalid URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frontend branching&lt;/li&gt;
&lt;li&gt;i18n key lookup&lt;/li&gt;
&lt;li&gt;user-facing messaging&lt;/li&gt;
&lt;li&gt;predictable API contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But internally, that same error should also be able to carry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which variable was invalid&lt;/li&gt;
&lt;li&gt;where it originated&lt;/li&gt;
&lt;li&gt;what the root cause was&lt;/li&gt;
&lt;li&gt;what remediation makes sense&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where contextual enrichment becomes important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_spin_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Cow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SpinEnvironmentError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;spin_sdk&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Cow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Owned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.or_else&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Cow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Borrowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ok_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;SpinEnvironmentError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;variable_not_set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nf"&gt;.with_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Variable: {name}"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="nf"&gt;.with_help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"Set the `spin` variable `{name}` to configure the service."&lt;/span&gt;
                    &lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The place that has context should be able to attach it without changing the public contract of the error.&lt;/p&gt;

&lt;p&gt;That matters for four reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; — internal infrastructure details should not leak automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; — operators still need enough information to diagnose incidents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API stability&lt;/strong&gt; — clients should depend on stable codes, not incidental internals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composability&lt;/strong&gt; — lower layers can classify; upper layers can enrich.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Problem 5: richer diagnostics should not make the happy path worse
&lt;/h2&gt;

&lt;p&gt;A lot of useful error context is expensive to compute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formatted strings&lt;/li&gt;
&lt;li&gt;rendered identifiers&lt;/li&gt;
&lt;li&gt;derived messages&lt;/li&gt;
&lt;li&gt;remediation text&lt;/li&gt;
&lt;li&gt;partially serialized payload fragments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that work happens eagerly, the success path pays for formatting it will never use.&lt;/p&gt;

&lt;p&gt;That is why fluent enrichment matters, and why lazy variants are important:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lazy enrichment keeps formatting work on the error path.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_user_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.with_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to retrieve user configuration"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.with_help_fn&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Documentation: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/docs"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would avoid calling this "zero-cost" in the absolute sense, because nothing nontrivial in systems software is literally free. But it is fair to say that this design keeps enrichment cost conditional on failure, which is exactly where that cost belongs.&lt;/p&gt;

&lt;p&gt;That trade-off is central to the crate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the success path stays simple&lt;/li&gt;
&lt;li&gt;the failure path becomes much more descriptive&lt;/li&gt;
&lt;li&gt;the API remains typed rather than stringly-typed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Problem 6: backtraces are useful, but not sufficient as a design center
&lt;/h2&gt;

&lt;p&gt;A lot of Rust error discussions eventually converge on backtraces.&lt;/p&gt;

&lt;p&gt;Backtraces are useful. But they are not the only useful representation of failure, and they are not always the most practical one in restricted or highly structured environments.&lt;/p&gt;

&lt;p&gt;In NEXUS, I cared less about "capture every frame" and more about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable machine-readable classification&lt;/li&gt;
&lt;li&gt;a clean source chain&lt;/li&gt;
&lt;li&gt;structured context&lt;/li&gt;
&lt;li&gt;operator-readable output that works well in logs and terminals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That leads naturally to a metadata-chain approach rather than a backtrace-first model.&lt;/p&gt;

&lt;p&gt;A tree-style report is often more useful to humans than a dense debug dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;× [DB_CONN_LOST]: Connection lost
    Status: 503 Service Unavailable | Target: database-service
  │
  ├─ Caused by:
  │  1: Timed out waiting for connection pool
  │  2: No route to host (os error 113)
  │
  ├─ Details:
  │  Failed to connect to cluster: production-01
  │
  ╰─ Help: Restart the database proxy or check the VPC security group.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This format is not trying to replace low-level debugging tools. It is trying to optimize the first few minutes of understanding a production failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why macros were the right mechanism
&lt;/h2&gt;

&lt;p&gt;The interesting part of &lt;code&gt;nx-error&lt;/code&gt; is not merely that it uses macros. Plenty of Rust crates do.&lt;/p&gt;

&lt;p&gt;What mattered here was using a procedural macro to centralize the repetitive parts of correctness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;constructor generation&lt;/li&gt;
&lt;li&gt;metadata defaults&lt;/li&gt;
&lt;li&gt;source conversions&lt;/li&gt;
&lt;li&gt;naming conventions&lt;/li&gt;
&lt;li&gt;transparent propagation&lt;/li&gt;
&lt;li&gt;extension API ergonomics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of those are tasks humans can handle manually, but rarely with perfect consistency across time and crates.&lt;/p&gt;

&lt;p&gt;Procedural macros turned out to be the right tool because they let the crate encode design rules once and apply them uniformly everywhere else.&lt;/p&gt;

&lt;p&gt;That is especially valuable in foundational infrastructure crates. The value of the macro is not metaprogramming itself, but the ability to enforce consistency across crates.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short comparison with the usual alternatives
&lt;/h2&gt;

&lt;p&gt;The natural question is: why not just combine &lt;code&gt;thiserror&lt;/code&gt;, &lt;code&gt;anyhow&lt;/code&gt;, and a custom response mapper?&lt;/p&gt;

&lt;p&gt;For many systems, that is the correct answer.&lt;/p&gt;

&lt;p&gt;For NEXUS, I wanted a tighter integration between typed domain errors and operational metadata, so the comparison looked more like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;&lt;code&gt;anyhow&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;thiserror&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;nx-error&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typed domain errors&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable machine codes&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;First-class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public vs private context split&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Explicit design goal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layered propagation semantics&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Macro-assisted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context enrichment API&lt;/td&gt;
&lt;td&gt;Ad hoc&lt;/td&gt;
&lt;td&gt;Ad hoc&lt;/td&gt;
&lt;td&gt;First-class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WASM-conscious error contract&lt;/td&gt;
&lt;td&gt;Not a primary goal&lt;/td&gt;
&lt;td&gt;Not a primary goal&lt;/td&gt;
&lt;td&gt;Primary constraint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The point is not that one of these approaches is universally better; it is that they optimize for different constraints.&lt;/p&gt;

&lt;p&gt;If I were building a small service or a conventional CLI, I would likely reach for a simpler stack. But NEXUS needed a crate that could act as a shared failure contract across multiple layers and environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;nx-error&lt;/code&gt; came first in NEXUS
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nx-error&lt;/code&gt; was not designed in isolation. It was designed as the base contract layer for the rest of NEXUS.&lt;/p&gt;

&lt;p&gt;That meant it had to support, from the beginning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;typed library errors&lt;/li&gt;
&lt;li&gt;application-level response mapping&lt;/li&gt;
&lt;li&gt;safe external serialization&lt;/li&gt;
&lt;li&gt;rich internal diagnostics&lt;/li&gt;
&lt;li&gt;layered propagation&lt;/li&gt;
&lt;li&gt;low-friction context enrichment&lt;/li&gt;
&lt;li&gt;compatibility with constrained or WASM-adjacent environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I started thinking of errors as part of the platform contract rather than local implementation detail, the ordering became obvious.&lt;/p&gt;

&lt;p&gt;The first crate in a system like this should reduce entropy for everything that follows.&lt;/p&gt;

&lt;p&gt;That is what &lt;code&gt;nx-error&lt;/code&gt; was built to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical examples
&lt;/h2&gt;

&lt;p&gt;Here are a few small examples of the patterns &lt;code&gt;nx-error&lt;/code&gt; is designed to support in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: a domain error with stable API semantics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nx_error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[error]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;UserError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(&lt;/span&gt;
        &lt;span class="nd"&gt;message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"User not found"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"USER_NOT_FOUND"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;#[error(&lt;/span&gt;
        &lt;span class="nd"&gt;message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"User profile is invalid"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"USER_PROFILE_INVALID"&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;InvalidProfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 2: wrapping infrastructure failures while preserving the source
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;nx_error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[error]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ConfigError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(&lt;/span&gt;
        &lt;span class="nd"&gt;message&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Failed to load configuration file"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;status&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;code&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CONFIG_LOAD_FAILED"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;source&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;std::io::Error&lt;/span&gt;
    &lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;read_config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConfigError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.toml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ConfigError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 3: attach remediation where it becomes meaningful
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;require_service_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;url&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SpinEnvironmentError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_spin_var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.with_help_fn&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Variable `{name}` must be set to a valid URL (e.g., http://target.internal)"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nn"&gt;url&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SpinEnvironmentError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Provide a fully qualified URL such as http://service.internal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What building &lt;code&gt;nx-error&lt;/code&gt; changed in how I think about error handling
&lt;/h2&gt;

&lt;p&gt;The biggest lesson was that error handling is not a side concern in systems software. It shapes at least four parts of the system simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the runtime model&lt;/li&gt;
&lt;li&gt;the API contract&lt;/li&gt;
&lt;li&gt;the observability strategy&lt;/li&gt;
&lt;li&gt;the operator experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good error abstraction should make all four more coherent.&lt;/p&gt;

&lt;p&gt;For NEXUS, that meant &lt;code&gt;nx-error&lt;/code&gt; had to do more than derive &lt;code&gt;Display&lt;/code&gt; and &lt;code&gt;Error&lt;/code&gt;. It had to bridge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;typed Rust code&lt;/li&gt;
&lt;li&gt;safe client-facing responses&lt;/li&gt;
&lt;li&gt;structured operational diagnostics&lt;/li&gt;
&lt;li&gt;low-friction propagation across layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why this crate came first.&lt;/p&gt;

&lt;p&gt;Not because it is flashy, but because nearly every other crate in the system depends on getting failure semantics right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nx-error&lt;/code&gt; emerged from a fairly specific set of constraints: typed domain errors, stable machine-readable metadata, safe external serialization, rich internal diagnostics, and predictable behavior across layered Rust services.&lt;/p&gt;

&lt;p&gt;Those constraints pushed the design toward a metadata-centric, macro-assisted error model. The goal was not to invent a new philosophy of error handling, but to reduce the repetitive and lossy work that tends to appear once systems grow beyond a few modules.&lt;/p&gt;

&lt;p&gt;As the first crate in NEXUS, &lt;code&gt;nx-error&lt;/code&gt; set the tone for the rest of the project: make contracts explicit, keep failure observable, and avoid paying for complexity in places where the runtime does not benefit from it.&lt;/p&gt;

&lt;p&gt;In the next post, I’ll cover another foundational part of NEXUS and show how the same constraints shaped its design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;This is just the beginning. In the next part, I'll dive into how NEXUS handles distributed state. Follow the repository to stay tuned.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main Repository:&lt;/strong&gt; &lt;a href="https://github.com/AnatoliiShliakhto/nexus" rel="noopener noreferrer"&gt;NEXUS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crates:&lt;/strong&gt; &lt;a href="https://github.com/AnatoliiShliakhto/nexus/tree/dev/crates/error" rel="noopener noreferrer"&gt;&lt;code&gt;nx-error&lt;/code&gt;&lt;/a&gt; | &lt;a href="https://github.com/AnatoliiShliakhto/nexus/tree/dev/crates/error-macros" rel="noopener noreferrer"&gt;&lt;code&gt;nx-error-macros&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>architecture</category>
      <category>backend</category>
      <category>webassembly</category>
    </item>
  </channel>
</rss>
