
Designing a shared error contract in Rust (Building NEXUS, Part 1)
Hi all,
I recently wrote up a technical breakdown of an error-handling approach I ended up building for a project called Nexus:
Errors as Infrastructure: Why the first crate in NEXUS wasn't networking
I started with the usual assumption that thiserror / anyhowwould cover most of what I needed. And for many projects, I think they absolutely do.
But in this case I wanted errors to work as a shared contract across multiple layers: service internals, HTTP/API responses, frontend-facing boundaries, and some more constrained runtime environments as well.
The main issues I kept running into were:
- error types getting heavier as more context was added
- too much manual mapping between “safe for clients” and “useful for operators”
- repetitive conversion glue between layers
- inconsistent error codes/messages/statuses over time
So I ended up building a small metadata-centric error crate and wrote up the design trade-offs behind it.
The write-up focuses on:
- keeping result/error contracts relatively thin
- attaching rich diagnostic context without stuffing everything into enum variants
- separating public error output from internal diagnostic detail
- preserving semantics across propagation
- using proc macros to reduce semantic drift
It’s definitely NOT meant as “everyone should stop using thiserror” It’s more about a narrower design problem: how to keep errors consistent and structured when they need to cross backend, API, frontend, and runtime boundaries.
I’d be interested in how others approach this in real projects.
Do you usually keep errors typed all the way through?
Do you standardize error metadata at the API boundary only?
How do you handle public vs internal error views without a lot of repetitive glue?
Would love to hear other approaches.