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.