eac7e5b81d
All eight implementation steps from ADR-0019's §"Order of
operations":
Step 1 — `src/friendly/` module skeleton; `t!()` macro; YAML
catalog loader (`include_str!` + `serde_yml`); `{name}`
substitution helper that rejects format specifiers per §8.4.
Step 2 — `error.*` catalog populated for UNIQUE / FK /
NOT NULL / CHECK / type-mismatch / not_found / already_exists /
generic / invalid_value, with verbose hints per
pedagogical-voice rule (§5). Anchor phrases (§10) preserved
verbatim.
Step 3 — `FriendlyError { headline, hint, diagnostic_table }`
+ renderer composing the three blocks per §7.
Step 4 — `translate(&DbError, &TranslateContext) → FriendlyError`.
Classifies by `SqliteErrorKind` first, then by message text
for the constraint family. `change column` failures route to
the type-mismatch headline, subsuming the previous
`friendly_change_column_engine_error` helper.
Step 5 — `DbError::friendly_message()` delegates to the
translator with default context. Removed
`friendly_change_column_engine_error` (absorbed) and
`enrich_fk_message` (FK list moves to the deferred re-query
step). One test rewritten to assert on the engine-classified
payload rather than the removed enrichment text.
Step 6 — `messages (short|verbose)` app-level command parallel
to `mode`. `App::messages_verbosity` (default verbose)
threaded into `TranslateContext` via
`App::build_translate_context`. `AppEvent::DslFailed` now
carries the structured `DbError`, plus the App extracts the
user's attempted value from `Command::Insert` / `Update`
to fill the `{value}` placeholder for UNIQUE / NOT NULL.
Step 7 — Catalog validator (§8.6) checks for missing keys,
unused/undeclared placeholders, format specifiers, and
forbidden engine vocabulary. `main.rs` parses the embedded
catalog at startup so a corrupted build artefact fails
loudly there rather than at the first `t!()` call.
Step 8 — Anchor phrases (§10) held: existing tests asserting
on "no such table", "already exists", "cannot be converted",
etc. all pass without rewording.
## Tally
603 tests passing (was 561: +42 net). Clippy clean with
nursery lints. Release binary 7.7 MB.
## Deliberately deferred
- Schema-aware enrichment for FK violations (parent_table /
parent_column / child_table) and the multi-value
natural-order INSERT case for UNIQUE. Both need the
Database handle in scope at translation time, so they
bundle naturally with the row-pinpoint re-query work
(ADR-0019 §6) — that follow-on adds runtime-side
enrichment via a `Database` lookup and a structured
failure-context carried on `DslFailed`. Until then,
unfilled placeholders render as their `{name}` form for
visual consistency with the catalog.
- Migration sweep (§9). Only `error.*` is catalog-driven so
far; `help.*`, `ok.*`, `client_side.*`, `replay.*`,
`parse.*`, modal labels, etc. migrate per-PR.
- Settings persistence for `messages`. In-session state for
now; waits on the future settings ADR.
143 lines
4.8 KiB
Rust
143 lines
4.8 KiB
Rust
//! Events fed into the application's update function.
|
|
//!
|
|
//! `AppEvent` is the single input type the runtime delivers to
|
|
//! `App::update`. Synthetic instances drive Tier 3 integration
|
|
//! tests (see ADR-0008), so the type is plain data with no
|
|
//! runtime dependency.
|
|
|
|
use crossterm::event::KeyEvent;
|
|
|
|
use crate::db::{
|
|
AddColumnResult, ChangeColumnTypeResult, DataResult, DbError, DeleteResult,
|
|
InsertResult, TableDescription, UpdateResult,
|
|
};
|
|
use crate::dsl::Command;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum AppEvent {
|
|
Key(KeyEvent),
|
|
Resize {
|
|
cols: u16,
|
|
rows: u16,
|
|
},
|
|
Tick,
|
|
/// A DSL command finished successfully. `description` is
|
|
/// `Some` for commands that produce a table view (create,
|
|
/// add column) and `None` for commands that don't (drop).
|
|
DslSucceeded {
|
|
command: Command,
|
|
description: Option<TableDescription>,
|
|
},
|
|
/// A `show data` query succeeded.
|
|
DslDataSucceeded { command: Command, data: DataResult },
|
|
DslInsertSucceeded {
|
|
command: Command,
|
|
result: InsertResult,
|
|
},
|
|
DslUpdateSucceeded {
|
|
command: Command,
|
|
result: UpdateResult,
|
|
},
|
|
DslDeleteSucceeded {
|
|
command: Command,
|
|
result: DeleteResult,
|
|
},
|
|
/// A `change column …` succeeded. `result` carries both the
|
|
/// post-rebuild description (for the auto-show) and the
|
|
/// optional `[client-side]` note (ADR-0017 §6).
|
|
DslChangeColumnSucceeded {
|
|
command: Command,
|
|
result: ChangeColumnTypeResult,
|
|
},
|
|
/// An `add column …` succeeded. `result` carries the
|
|
/// post-add description plus any `[client-side]` notes
|
|
/// from the auto-fill paths (ADR-0018 §9).
|
|
DslAddColumnSucceeded {
|
|
command: Command,
|
|
result: AddColumnResult,
|
|
},
|
|
/// A DSL command failed. `error` is the structured
|
|
/// payload — App applies its current verbosity setting
|
|
/// (`messages_verbosity`) when rendering through
|
|
/// `friendly::translate_error` (ADR-0019 §5).
|
|
DslFailed {
|
|
command: Command,
|
|
error: DbError,
|
|
},
|
|
/// Refreshed list of tables in the database.
|
|
TablesRefreshed(Vec<String>),
|
|
/// A persistence failure occurred (ADR-0015 §8). The
|
|
/// application surfaces a fatal banner and exits cleanly so
|
|
/// the message remains above the shell prompt.
|
|
PersistenceFatal {
|
|
operation: String,
|
|
path: std::path::PathBuf,
|
|
message: String,
|
|
},
|
|
/// Runtime has computed the rebuild summary from
|
|
/// `project.yaml` + `data/` and is ready for the user to
|
|
/// confirm. App opens the confirmation modal.
|
|
RebuildPrepared {
|
|
summary: String,
|
|
},
|
|
/// Rebuild completed successfully. App closes the modal,
|
|
/// surfaces a friendly outcome message, and refreshes the
|
|
/// table list.
|
|
RebuildSucceeded {
|
|
summary: String,
|
|
},
|
|
/// Rebuild failed in a non-fatal way (e.g., user-visible
|
|
/// constraint problem) — surfaced like other DSL failures.
|
|
RebuildFailed {
|
|
error: String,
|
|
},
|
|
/// Runtime has gathered the list of available projects
|
|
/// for the load picker. App opens the picker modal.
|
|
LoadPickerReady {
|
|
entries: Vec<crate::app::LoadPickerEntry>,
|
|
},
|
|
/// A project switch (load / new / save-as / import)
|
|
/// succeeded. Carries the new display name, the temp
|
|
/// flag (drives the `[TEMP]` status-bar prefix), and the
|
|
/// seed entries for input-history hydration off the new
|
|
/// project's `history.log` (I2-persist, ADR-0015 §12).
|
|
ProjectSwitched {
|
|
display_name: String,
|
|
is_temp: bool,
|
|
history_entries: Vec<String>,
|
|
},
|
|
/// A project switch failed in a non-fatal way (target
|
|
/// already exists, path unreadable, …). Surfaced as an
|
|
/// error in the output panel.
|
|
ProjectSwitchFailed {
|
|
error: String,
|
|
},
|
|
/// Export wrote a zip successfully. Carries the resolved
|
|
/// final path so the user gets a "wrote to: …" note.
|
|
ExportSucceeded {
|
|
path: std::path::PathBuf,
|
|
},
|
|
/// Export failed in a non-fatal way (target exists, IO
|
|
/// error, sequence range exhausted, …).
|
|
ExportFailed {
|
|
error: String,
|
|
},
|
|
/// A `replay <path>` finished without error, after running
|
|
/// `count` non-blank, non-comment commands from the file.
|
|
/// Surfaced as `[ok] replay — N command(s)` in the output.
|
|
ReplayCompleted {
|
|
path: String,
|
|
count: usize,
|
|
},
|
|
/// A `replay <path>` aborted at line `line_number`. `command`
|
|
/// is the line text as it appeared in the file (for the
|
|
/// user's eyeline so they can locate the failing entry);
|
|
/// `error` is the rendered parse or runtime error.
|
|
ReplayFailed {
|
|
path: String,
|
|
line_number: usize,
|
|
command: String,
|
|
error: String,
|
|
},
|
|
}
|