Diagnose a failed asset lookup¶
Reads in this crate fail quietly by design: a missing file, an
unreadable file, a rejected path and a layer that was never registered
all produce the same None. That makes for a clean overlay and a
frustrating half-hour, so start here.
Print the stack first¶
Assets implements Debug and prints its layer labels in priority
order, lowest first:
This answers two questions at once: whether the layer you expected is registered at all, and whether it is where you thought in the order. A surprising number of lookups fail because the answer is "no" to one of those.
If a label looks like a filesystem path — Assets { layers:
["/etc/mytool"] } — the arguments to .directory() are the wrong way
round. It takes the root first and the label second, unlike .memory().
open returns None and the file is definitely there¶
Work down this list; it is roughly ordered by how often each one is the answer.
The path is keyed differently from how you asked. Embedded and
in-memory layers compare keys literally. ./config.yaml,
/config.yaml and config.yaml are three different keys, and only the
last matches an embed of config.yaml. A DirectorySource is more
forgiving — it ignores a leading ./ — so a path with ./ on the
front can work in development and fail once it falls through to the
embedded layer.
The path is a directory. Directories are listable, never readable.
exists returns false for them too, even when list_dir on the
parent shows the name.
The path contains ... DirectorySource rejects .. anywhere,
even where it would not escape the root: sub/../file.txt is refused.
So is any absolute path.
The DirectorySource root does not exist. A missing root is not an
error — the layer simply contributes nothing, forever. Check the path
you passed actually exists, and that it is the directory rather than a
file inside it.
The file cannot be read. Permission denied is indistinguishable from absent. If it is readable by you but not by the process, this is the one.
The embedded folder did not include it. If your embed type carries
#[include] or #[exclude] attributes, exclude beats include.
Iterate E::iter() directly to see what actually got embedded.
The embedded asset was edited but not rebuilt. This crate enables
rust-embed's debug-embed, so there is no read-from-disk behaviour in
debug builds. The binary holds whatever was there at compile time.
The right file is found but the wrong bytes come back¶
Byte reads take the last registered layer that has the path, not
the first. If the wrong version is winning, a layer you meant to be a
fallback is registered after the one you meant to win. Reorder the
builder calls; there is no way to reorder after build().
list_dir misses entries, or shows things I cannot read¶
Listings are unioned across every layer, deduplicated, and byte-order sorted — so uppercase names sort before lowercase ones.
Entries include subdirectory names with nothing to mark them as
directories, and exists on a directory name returns false. If you
are iterating a listing and reading each name, expect the directories
to come back empty and skip them.
Nested paths are not flattened: d/sub/b.txt contributes sub to
list_dir("d"). There is no recursive listing.
A merged config has a key missing entirely¶
Almost always RFC 7396's deletion rule: a higher layer set the key to
null, which removes it rather than setting it to null. Search the
override files for null, ~, and bare keys with nothing after the
colon — key: with an empty value parses as null too.
A merged config came back empty, or failed with "invalid type: null"¶
A higher layer's file is empty or contains only comments. An empty YAML
document parses to null, and a null patch replaces everything below it.
Put {} in the file, or delete it.
A merged array lost the defaults¶
Arrays replace wholesale. A user override listing one plugin replaces the shipped list entirely rather than appending to it. This is RFC 7396 and there is no option to change it — see Merge semantics for the alternative schema shape.
A number came out as null¶
Check for .inf, -.inf or .nan in the YAML. The YAML-to-JSON
conversion turns all three into null with no warning.
A parse error names a file I did not expect¶
The path in an AssetError::Parse reads
cfg.yaml (layeruser) when a single layer failed to parse, and the
bare cfg.yaml when the merged document did not fit the target type.
The second form means every individual file is well-formed and it is
their combination that does not fit — usually a required field deleted
by a null, or a value out of range for its type.
A malformed file in any contributing layer aborts the whole load. There is no fallback to the defaults, so a broken user override is loud rather than silent.
Related¶
AssetErrorreference — every variant and what raises it.- Merge semantics — the full rule table.
- Why path traversal is rejected lexically — why a rejected path looks exactly like a missing one.