Validation
Checking what your views read and raise against what the store publishes, in two grades.
The store knows what every scope provides and accepts. Your views know what they read and raise. Validation puts the two side by side and grades every disagreement, so a view that reads a field nobody provides fails a test instead of drawing an empty label.
Two grades
Every finding either refuses or reports.
- Refuses means a view expects something that is not there. It reads a field no scope provides, or raises an intent no scope accepts. Left alone, this is a screen that draws perfectly and shows nothing, or a button that reaches nobody. It should be loud.
- Reports means coverage drift: a field nothing reads, an intent nothing raises, a route no view draws. That is usually a developer part-way through a change, or a scope deliberately publishing more than the shipped views use. It should be visible, but it should not stop a build.
The grades travel together. A run that refuses one stale field also lists the four fields nobody reads.
What Carte does and does not do
Carte has no idea what your views are. It reads no files and parses nothing. A "document" here is whatever declares what a view reads: a template, a view module, a file in a UI language. You describe each one to Carte as a list of field names and raises.
So validation needs a harness: a test in your app that walks your view definitions, turns each into those lists, and hands them over. With a UI language that can report its own selections, the harness is mechanical. With hand-written Rust views, you list them yourself.
TODO: no ready-made harness ships for any UI layer yet, including egui and Masque.
Running a check
use carte::{Declared, Kind, Scope, Store, Validation};
#[test]
fn views_match_the_store() {
let store = build_store().unwrap(); // your app's setup
let mut check = Validation::new(&store);
let note = [EDITOR, Scope::Process]; // nearest first
check.selects_named("note", ¬e, &["title".into(), "words".into()]);
check.raises("note", ¬e, &[Declared::new("rename", vec![Kind::Str])]);
check.draws("ui", &["home", "note"], table().ids());
let findings = check.finish();
assert!(!findings.refuses(), "{findings}");
}
Each call names a document and the scopes it reads from, nearest first: the view's own scope, then whatever encloses it, then Scope::Process. Which scopes those are is an input. Carte does not work it out for you.
| Method | Checks |
|---|---|
selects_named(doc, scopes, names) | Each name is a field some scope provides |
selects(doc, scopes, fields) | The same, and that each Field's shape matches the provider's |
reads(doc, scopes, paths) | Each dotted path such as "card.title", where card is a record field, reaches a real field |
raises(doc, scopes, declared) | Each raise is accepted, with the right arguments |
draws(doc, declared, registered) | The document's screens against the route ids it should draw |
unmounted(doc, scope_name, scopes) | Records a read from a scope the document is not under |
publishes(scope) | Whether the store publishes anything for a scope |
finish() adds the findings that are about the whole set rather than one document: fields nothing read and intents nothing raised. It returns Findings.
Findings
| Finding | Grade | Means |
|---|---|---|
Unprovided | Refuses | A selected field no scope provides |
Shape | Refuses | A field provided with a different shape |
Unreached | Refuses | A dotted read that names a missing field |
Unaccepted | Refuses | A raise no scope accepts |
Raise | Depends on the Drift | A raise with wrong arguments, or similar |
Unpublished | Refuses | A scope in the list that publishes nothing |
Unmounted | Refuses | A read from a scope the document is not under |
Shadowed | Reports | Two scopes provide the field; the nearer one wins |
Unread | Reports | A provided field nothing selects |
Unraised | Reports | An accepted intent nothing raises |
Undrawn | Reports | A route the document draws no screen for |
Unrouted | Reports | A screen no route reaches |
Each finding names its document, field, intent or screen. Finding::refuses() gives its grade, and Finding::document() the document it is about, if any. Findings has all(), refusals(), reports(), refuses() and is_empty(), and prints grouped under refuses: and reports: headings.
What it cannot see
Validation refuses only what it can see. A path into a list, a map or a union cannot be followed past that point, because a collection is one field. A read through a helper's parameter or a computed key is invisible. Both are silent, not reported. A refusal that a correct view cannot avoid is a check people switch off.
Before naming a node in a scope list, ask publishes(scope). Most view nodes own no scope, and naming one that publishes nothing is an Unpublished refusal.
Next
- Design principles: why the grades are drawn where they are.
- Status and roadmap: what is still missing.