rtb-assets¶
rtb-assets lets a Rust binary ship its own defaults and let users
override them. It unifies three kinds of asset storage behind one
read-only API:
- Embedded via
rust-embed— files compiled into the executable. Default configs, templates, docs. - Physical directory — system-wide and per-user override directories on disk.
- In-memory — test fixtures and scaffolder scratch space.
Byte reads follow last-wins shadowing: the highest-priority layer that has the file supplies the whole file. Structured reads (YAML, JSON) follow RFC 7396 deep merge, so an override file states only the keys it changes.
Part of the phpboyscout Rust toolkit; extracted from — and used by — rust-tool-base.
use rtb_assets::Assets;
let assets = Assets::builder()
.embedded::<Defaults>("defaults") // lowest priority
.directory("/etc/mytool", "system")
.directory(user_config_dir, "user") // highest priority
.build();
let icon = assets.open("icons/app.png"); // user's, if they have one
let config: Config = assets.load_merged_yaml("config.yaml")?; // all three, merged
Where to start¶
| If you want to… | Read |
|---|---|
| Get a working binary with overridable defaults | Ship defaults a user can override |
| Design a config file that merges well | Merge layered configuration |
| Add a layer the built-ins do not cover | Write a custom asset source |
| Test code that reads assets | Use assets in tests |
| Work out why a read returned nothing | Diagnose a failed asset lookup |
| Look up a method, an error or a default | Reference |
| Understand why it works this way | Why an overlay filesystem |
| Find out whether it does the thing you need | What rtb-assets does not do |
The public API at a glance¶
| Item | Purpose |
|---|---|
Assets |
Read-only overlay handle. open, open_text, exists, list_dir, load_merged_yaml, load_merged_json. Clone is refcount-only. |
AssetsBuilder |
Registers layers in priority order, later wins: embedded, directory, memory, source. |
AssetSource |
Trait for custom layers — read, list, and a diagnostic name(). |
EmbeddedSource<E> |
Layer over a rust-embed type — compile-time bundled defaults. |
DirectorySource |
Layer over a directory on disk — user and system overrides. |
MemorySource |
Layer over a HashMap<String, Vec<u8>> — tests, scaffolder scratch. |
AssetError |
NotFound, NotUtf8, Parse — each a miette::Diagnostic with a stable code. |
Full signatures and failure behaviour: Assets and AssetsBuilder
reference. Rustdoc: docs.rs/rtb-assets.
What it does not do¶
It cannot write, delete or watch. It cannot tell you which layer
supplied a value. It merges YAML and JSON and no other format, arrays
replace rather than append, and null in an override deletes a key
rather than setting it. The full list, with the reasoning and the
workarounds, is What rtb-assets does not do
— worth reading before you adopt the crate rather than after.
Path traversal is rejected lexically¶
Rejection is silent, and symlinks are not checked
DirectorySource refuses any path containing .., any absolute
path, and any Windows prefix component, before it touches the
filesystem. open("../../etc/passwd") returns None.
The check is lexical, not canonicalize()-based: a symlink inside
an otherwise-trusted root still resolves outside it. And rejection
is indistinguishable from a missing file — nothing logs, and no
distinct error variant exists.
Why path traversal is rejected lexically sets out the threat model and the boundary.
How it is tested¶
The behaviour on this site is covered by 14 unit tests in tests/unit.rs
(T1–T14, including path traversal in T14) and 6 Gherkin scenarios
in tests/features/assets.feature (S1–S6) driven by a
Cucumber runner. just ci
runs the same gate CI does: fmt-check, clippy -D warnings,
cargo doc, tests, and cargo deny.
Further reading¶
The blog carries a curated route through this subject: Rust, and what survived the port collects everything written about it, ordered so you can start at the beginning rather than newest-first.
Ask phpbotscout

He answers questions about the projects over on the Discord, citing the docs where they already cover it, and offering to raise an issue where they don't. Bring a bug, an idea, or a questionable engineering decision.