Skip to content

AssetError reference

AssetError is the only error type the crate produces. It is #[non_exhaustive], derives thiserror::Error and miette::Diagnostic, and every variant carries a stable diagnostic code under the rtb::assets:: namespace.

#[non_exhaustive]
pub enum AssetError {
    NotFound(String),
    NotUtf8 { path: String },
    Parse { path: String, format: &'static str, message: String },
}

Because the enum is #[non_exhaustive], a match on it needs a _ arm; a new variant in a future release will not break your build.

Only three methods return a Result: open_text, load_merged_yaml and load_merged_json. open, exists and list_dir cannot fail — they return None, false and an empty Vec respectively.

NotFound

asset not found: templates/readme.md

Diagnostic code rtb::assets::not_found. The payload is the requested path, unchanged.

Raised when no registered layer provides the path. Causes, in rough order of likelihood:

  • The path does not match how the asset is keyed. Embedded and in-memory layers compare keys literally, so a leading ./, a leading /, or a backslash separator will not match.
  • The layer you expected to hold it was never registered, or was registered with its arguments swapped — print the Assets with {:?} and check the labels.
  • A DirectorySource root does not exist. That is not an error in itself; the layer simply contributes nothing.
  • The path names a directory. Directories are listable, not readable.

NotUtf8

asset `icons/app.png` is not valid UTF-8

Diagnostic code rtb::assets::not_utf8. Raised only by open_text, when a layer provided bytes that are not valid UTF-8.

The bytes are discarded rather than lossily converted. Read binary assets with open, which returns Option<Vec<u8>> and never inspects the contents.

A UTF-8 byte-order mark does not trigger this — the BOM is valid UTF-8 and arrives as a leading \u{feff} in the returned String. Strip it yourself if the consumer of the string will not tolerate it.

Parse

failed to parse asset `cfg.yaml (layer `user`)` as YAML: did not find expected key

Diagnostic code rtb::assets::parse, with the help text "verify the file is well-formed YAML" (or JSON). Three fields:

Field Contents
path either <path> (layer
format "YAML" or "JSON"
message the underlying parser's message, verbatim

Raised in three distinct situations, which the path field distinguishes:

  1. A contributing layer's file is malformed. path names the layer. The load aborts; there is no fallback to lower layers.
  2. A contributing layer's YAML bytes are not UTF-8. Also names the layer, also reported as a parse failure rather than NotUtf8.
  3. The merged document does not fit T. path is bare, because merging has already happened and no single layer can be blamed. A missing required field, a number out of range for its type, or a type mismatch all land here.

The path field is a human-readable description, not a path you can feed back into open. If you need the clean path for a retry or a log field, keep hold of the string you passed in.

Rendering an AssetError

Display gives the one-line message above. miette's report handler gives the code and help text as well, which is what a CLI built on the toolkit will print:

match assets.load_merged_yaml::<Config>("config.yaml") {
    Ok(config) => config,
    Err(e) => return Err(miette::Report::new(e)),
}

The diagnostic codes are part of the public contract and are safe to match on or document in a tool's own troubleshooting guide.

What is deliberately not an error

  • A missing layer root. A DirectorySource pointed at a directory the user has not created behaves as an empty layer.
  • A rejected path. ../../etc/passwd returns None from open and NotFound from open_text — the same as any absent file, with no indication that a security rule fired. See Why path traversal is rejected lexically for the reasoning.
  • An unreadable file. Permission denied is indistinguishable from absent, and a lower layer gets its turn.
  • An empty overlay. Assets::default() is valid and every read simply finds nothing.