Skip to content

What rtb-assets does not do

Every item here is a deliberate absence or a known constraint, checked against version 0.6.3. If you are evaluating the crate, read this page before the API reference — it is faster to find out here that something is not supported than to look for the method that would have done it.

It cannot write, create or delete

There is no write API of any kind, and none is planned. Assets is a read handle over a fixed stack. Tools that emit files — scaffolders, config set commands, cache writers — do their own writing with their own notion of where output belongs. Why an overlay filesystem sets out the reasoning.

It cannot tell you which layer supplied a path

open returns bytes. list_dir returns names. Neither says where the value came from, and there is no origin_of(path) or layers() accessor. The only visibility is Debug, which prints the layer labels but not their contents.

If your tool wants to show a user "this setting came from /etc/mytool/config.yaml", it must keep its own record of the roots it registered and check them itself. Layered configuration with provenance is a different problem, solved by a config crate, not by an asset overlay.

It cannot distinguish a file from a directory

list_dir returns bare names, mixing files and subdirectories with no marker. exists returns false for a directory. Probing with exists after a listing is the only way to tell them apart, and it costs a read attempt per name.

It has no metadata

No size, no modification time, no MIME type, no permissions, no checksum. A rust-embed EmbeddedFile carries metadata that EmbeddedSource discards on the way through, because a DirectorySource and a MemorySource cannot supply the same fields and a partial metadata API is worse than none.

It has no streaming reads

read returns an owned Vec<u8>. A 500 MB asset is a 500 MB allocation, in full, before the caller sees the first byte, and there is no Read/Seek handle to be had. The crate is built for config-and-template-sized files; large media belongs on disk with a path you open yourself.

It caches nothing and watches nothing

Every open on a DirectorySource re-reads from disk. There is no cache to invalidate — which also means there is nothing stale — and no file watching, no reload hook, and no notification when an override file changes. A tool that wants hot reload builds a new Assets when it decides something changed, and swaps it in.

Note also that a clone taken before the swap keeps the old stack. There is no way to mutate an Assets that other code is already holding.

The layer stack is fixed once built

No reordering, no insertion, no removal, no enabling and disabling. A --no-user-overrides flag has to be honoured at construction, by not registering the layer, rather than by turning it off later.

Only YAML and JSON are merged

load_merged_yaml and load_merged_json are the whole set. There is no TOML, INI, XML or .env loader, and no way to plug one in — the merge is not generic over a format trait.

A TOML file can still live in an overlay; read it with open_text and parse it yourself. It just gets last-wins shadowing rather than a merge, so a user override replaces the entire file.

Merging cannot express "set this to null"

The merge is RFC 7396, in which null in a higher layer deletes the key rather than setting it. There is no escape hatch. If your schema needs a genuine tri-state, encode it as a value the format can carry rather than as null. See Merge semantics.

Arrays cannot be extended by an override

Also RFC 7396: an array in a higher layer replaces the array below it entirely. There is no append, no merge-by-index, and no merge-by-key. A user who wants to add one entry to a list must restate the whole list, and will not pick up new default entries in later releases.

If that matters, model the collection as a map keyed by name rather than as a list. Maps merge; arrays do not.

YAML is converted to JSON before merging, and loses things on the way

.inf and .nan become null silently. Timestamps become strings. Explicit tags such as !!binary do not survive. yes and no are strings, not booleans, because serde_yaml follows the YAML 1.2 core schema. The full conversion table is in Merge semantics.

A single embedded folder cannot be split across layers

EmbeddedSource adapts one #[derive(RustEmbed)] type, and a RustEmbed type embeds one folder. Two embedded layers means two types and two folders. Filtering within a folder is done with rust-embed's own #[include]/#[exclude] attributes, not by anything this crate offers.

There is no dev-mode disk passthrough for embedded assets

rtb-assets enables rust-embed's debug-embed feature, which turns off the usual read-from-disk-in-debug-builds behaviour, for the whole dependency graph. Editing an embedded asset needs a rebuild. Layer a DirectorySource over the embedded one during development if you want live editing. See Crate features.

Path traversal protection has limits worth knowing

The DirectorySource check is lexical and does not follow symlinks, so a symlink inside an otherwise-trusted root reaches outside it. It is also silent — a rejected path is indistinguishable from a missing file. Why path traversal is rejected lexically covers the boundary in full.

It is not async, and layers must not block

Every method is synchronous. A custom source that performs a network fetch will block whatever thread the caller is on, including inside a merge that touches several layers. Cache behind the trait and decide there what a slow or failed fetch means; the trait has no way to report either.

Its only YAML parser is unmaintained upstream

serde_yaml 0.9.34 is published as 0.9.34+deprecated. It works, it passes the dependency audit today, and it is the only YAML implementation in the crate. Replacing it would change the conversion behaviour documented above, so treat that table as part of the contract, not as an implementation detail.