Define your own error type¶
Add the derives¶
Your crate needs thiserror and miette as direct dependencies — rtb-error
re-exports the Diagnostic trait and derive, but not thiserror::Error.
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Error, Diagnostic)]
#[non_exhaustive]
pub enum GreetError {
#[error("no name given")]
#[diagnostic(code(greet::no_name), help("pass a name: `greet Ada`"))]
NoName,
#[error("greeting file {path} is unreadable")]
#[diagnostic(code(greet::unreadable), help("check the path and permissions"))]
Unreadable {
path: String,
#[source]
source: std::io::Error,
},
}
#[error(...)] is the one-line message. code(...) is the machine-readable
identity a user can search for. help(...) is the line telling them what to do
next — write it as an instruction, not as a restatement of the problem.
Choose a code namespace¶
Use your tool's name as the first segment: greet::no_name, not
rtb::no_name. The rtb:: namespace belongs to rtb_error::Error and its
four scaffolding variants; reusing it makes two unrelated failures look like
the same one in a support conversation.
Mark the enum #[non_exhaustive]¶
Not required, but it is what the rest of the toolkit does, and for the same
reason: adding a variant later stops being a breaking change. The cost is that
callers must carry a wildcard match arm from day one.
Convert at the framework boundary¶
rtb_error::Error has exactly two automatic conversions —
From<std::io::Error> and From<Box<dyn Diagnostic + Send + Sync + 'static>>.
Your error is not one of them, so box it explicitly:
Box::new(e) coerces to the trait object at the call site. The error type must
be Send + Sync + 'static; if it is not, that line is where you find out.
Because Error::Other is #[diagnostic(transparent)], the user still sees
greet::no_name and your help text. The wrapper never appears.
Prefer not to convert at all¶
Boxing into Error::Other is for code that has to hand something back to
framework scaffolding expecting an rtb_error::Error. Most functions do not.
If yours returns to your own main, return your own type and let ? convert
it into a miette::Report directly:
fn run() -> miette::Result<()> {
greet(std::env::args().nth(1))?; // GreetError → Report, no boxing
Ok(())
}
miette::Report accepts any Diagnostic + Send + Sync + 'static, so the
umbrella enum is not on the path at all. Two things improve as a result: any
attached exit code stays readable, which
boxing into Error::Other would destroy, and the message is printed once
rather than
twice.
Raise a one-off diagnostic without a type¶
For a failure that happens in exactly one place and does not need a name, the
miette! macro builds a Report inline:
return Err(miette::miette!(
code = "greet::no_config",
help = "run `greet init` first",
"no config file at {}",
path.display()
));
It is a shortcut, not a replacement. An error a user might report to you should
have a code they can search for and a type you can match on.