rtb-assets 0.6.4

Embedded-asset + overlay filesystem abstraction. Part of the phpboyscout Rust toolkit.
Documentation
---
title: What rtb-assets does not do
description: The capabilities deliberately absent from the crate, the combinations that do not work, and what to reach for instead.
tags: [explanation, limitations]
---

# 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](why-an-overlay-filesystem.md) 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](../reference/merge-semantics.md#why-does-setting-a-key-to-null-remove-it).

## 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](../reference/merge-semantics.md#what-does-yaml-lose-on-the-way-through).

## 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](../reference/crate-features.md#debug-embed-is-on-and-that-changes-rust-embeds-default-behaviour).

## 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](path-safety.md) 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.