Error reference¶
ConfigError is the only error type the crate returns. It derives
thiserror::Error and miette::Diagnostic, so every variant carries a diagnostic
code in the rtb::config::* namespace and prints with miette's formatting when
your main returns miette::Result.
The enum is #[non_exhaustive]. A match over it needs a _ arm, and a future
release can add a variant without that being a breaking change.
Which variant means what¶
| Variant | Diagnostic code | Returned by |
|---|---|---|
Parse(String) |
rtb::config::parse |
build, reload |
Io { path, source } |
rtb::config::io |
build, reload |
Watch(String) |
rtb::config::watch |
watch_files |
Write(String) |
rtb::config::write |
write |
Schema(String) |
rtb::config::schema |
Nothing in the crate today — see below |
Watch, Write and Schema exist unconditionally even though the code that
constructs them is behind a Cargo feature. That is deliberate: a downstream match
does not have to be #[cfg]-gated to compile with the features off.
Parse — the merged configuration did not fit the struct¶
configuration error: invalid type: found string "not-a-number", expected u16 for key "PORT" in `MYTOOL_` environment variable(s)
The catch-all for anything the merge-and-deserialise step rejects. It carries the underlying message, which names the offending key and the layer it came from.
It covers more ground than the name suggests:
- Malformed YAML in a user file.
- A type mismatch —
port: "eighty"against au16, whether it came from a file or an environment variable. - A missing required field — any field without
#[serde(default)]that no layer supplied. - An unknown field, but only if your struct carries
#[serde(deny_unknown_fields)]. Without it, unknown keys are ignored. - A file that exists but cannot be read — a permissions failure arrives here as
Parse, wrapping the OS message, not asIo.
The diagnostic help text is check your config file and environment variables
against the schema.
Io — a config path exists but is not a regular file¶
Narrower than its name suggests. The crate returns Io in exactly one case: a path
registered with user_file exists on disk and is not a regular file — a
directory being the usual way to get here, typically by passing a config directory
where a file was wanted.
It carries the offending path, which is the useful part when several files are
registered, and an std::io::Error as its source.
A path that does not exist at all is not an error of any kind. A path that exists and
cannot be opened is a Parse
error.
Watch — the file watcher could not start¶
Only ever returned by watch_files, and only at start-up. Three causes:
| Message shape | Cause | Fix |
|---|---|---|
no user files registered |
The Config was built without any user_file path — including anything built by with_value or default() |
Register the file you want watched with ConfigBuilder::user_file |
watch <path>: No path was found. about ["<path>"] |
The registered path does not exist yet | Create the file before calling watch_files; the watcher cannot wait for a path to appear |
debouncer: … / spawn watcher thread: … |
The OS refused a watch handle or a thread | Check inotify limits (fs.inotify.max_user_watches on Linux) and process limits |
Once the watcher is running it never returns an error. A reload that fails after a bad save is swallowed on purpose so one broken write does not take the watcher down — but it also means a watcher that has stopped noticing changes reports nothing. See limitations.
Write — writing the config file failed¶
Returned by Config::write under the mutable feature, for both halves of the job:
- Serialisation failed — the message is prefixed with the format,
yaml:,toml:orjson:. TOML is the one that realistically fails, because it cannot represent every structure serde can. - The filesystem refused — creating the parent directory or writing the file. The message carries the path.
Schema — reserved for schema validation¶
Nothing in rtb-config constructs this variant today. It exists for consumers that
validate a candidate value against Config::schema before writing it — a
config set command checking an edit before it lands — so that the failure has a
home in the same error type rather than needing a second one.
If you are looking for the error that a bad value in a config file produces, that is
Parse.
How to handle these in a tool¶
ConfigError implements miette::Diagnostic, so the shortest useful handling is to
let it propagate:
fn main() -> miette::Result<()> {
let cfg = rtb_config::Config::<MyConfig>::builder()
.embedded_default(include_str!("../defaults.yaml"))
.user_file("/etc/mytool/config.yaml")
.env_prefixed("MYTOOL_")
.build()?;
// ...
Ok(())
}
That prints the message, the diagnostic code and the help line. Match on the enum
only when you want to act differently — for instance treating Io as "the user
pointed at a directory, re-prompt" while letting Parse fail the run.
Next¶
- API reference — which method returns which error
- Limitations — the failures that are silent rather than errors