diff --git a/src/app.rs b/src/app.rs index 283cd55..f62da1f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5922,6 +5922,17 @@ mod tests { assert!(output_contains(&app, "heart of SQL")); } + // ── Phase C batch 5: runtime error-class hints render ─────── + + #[test] + fn hint_renders_a_runtime_error_block() { + let mut app = App::new(); + app.last_error_hint_key = Some("unique".to_string()); + type_str(&mut app, "hint"); + submit(&mut app); + assert!(output_contains(&app, "must be unique")); + } + #[test] fn messages_command_toggles_verbosity_and_reports() { let mut app = App::new(); diff --git a/src/friendly/keys.rs b/src/friendly/keys.rs index e08ced5..bb945ff 100644 --- a/src/friendly/keys.rs +++ b/src/friendly/keys.rs @@ -234,6 +234,32 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[ ("hint.err.foreign_key.child_side.what", &[]), ("hint.err.foreign_key.child_side.example", &[]), ("hint.err.foreign_key.child_side.concept", &[]), + // Phase C batch 5 — runtime error-class hints. + ("hint.err.foreign_key.parent_side.what", &[]), + ("hint.err.foreign_key.parent_side.example", &[]), + ("hint.err.foreign_key.parent_side.concept", &[]), + ("hint.err.unique.what", &[]), + ("hint.err.unique.example", &[]), + ("hint.err.unique.concept", &[]), + ("hint.err.not_null.what", &[]), + ("hint.err.not_null.example", &[]), + ("hint.err.not_null.concept", &[]), + ("hint.err.check.what", &[]), + ("hint.err.check.example", &[]), + ("hint.err.check.concept", &[]), + ("hint.err.type_mismatch.what", &[]), + ("hint.err.type_mismatch.example", &[]), + ("hint.err.type_mismatch.concept", &[]), + ("hint.err.not_found.what", &[]), + ("hint.err.not_found.example", &[]), + ("hint.err.not_found.concept", &[]), + ("hint.err.already_exists.what", &[]), + ("hint.err.already_exists.example", &[]), + ("hint.err.already_exists.concept", &[]), + ("hint.err.generic.what", &[]), + ("hint.err.generic.example", &[]), + ("hint.err.invalid_value.what", &[]), + ("hint.err.invalid_value.example", &[]), // Phase C batch 1 — app-lifecycle command hints. ("hint.cmd.quit.what", &[]), ("hint.cmd.quit.example", &[]), diff --git a/src/friendly/strings/en-US.yaml b/src/friendly/strings/en-US.yaml index 81f1be5..8fd4cbb 100644 --- a/src/friendly/strings/en-US.yaml +++ b/src/friendly/strings/en-US.yaml @@ -592,11 +592,48 @@ hint: example: "explain select * from Customers where email = 'a@example.io'" concept: "Like simple mode's `explain`, but wraps a raw SQL statement. It reveals whether an index is used, and never executes." err: + # Runtime error classes (Phase C batch 5), keyed by + # friendly::error_hint_class. `example` is a fix recipe rather than a + # runnable line; `concept` is the relational idea behind the rule. foreign_key: child_side: what: "The value you gave for the child column doesn't match any parent row, so the foreign key has nothing to point at." example: "First insert the parent (insert into Customers …), then the child that references it." concept: "A foreign key is a promise that every child points at a real parent, so the parent must exist first. To allow orphans on delete instead, set the relationship's `on delete` to `set null` or `cascade`." + parent_side: + what: "You're deleting or changing a row that other rows point at, which would orphan those children." + example: "Delete the child rows first, or set the relationship's `on delete` to `cascade` (remove them too) or `set null` (keep them, unlinked)." + concept: "A foreign key guarantees every child has a real parent, so the database won't remove a parent out from under its children unless the relationship says what should happen to them." + unique: + what: "A value you're inserting — or updating to — already exists in a column that must be unique." + example: "Pick a different value, or update the existing row instead of inserting a new one." + concept: "A unique constraint (and every primary key) forbids duplicates, so each value identifies at most one row." + not_null: + what: "You left a column empty that is required to have a value." + example: "Supply a value for the column, or give it a default so new rows fill it automatically." + concept: "A `not null` constraint means every row must have a value there — it's how you mark a fact as mandatory." + check: + what: "A value broke a `check` rule defined on the column." + example: "Use a value the rule allows — for example a positive number, or one of the permitted options." + concept: "A `check` constraint is a condition every row must satisfy, so the database enforces business rules like \"price ≥ 0\" for you." + type_mismatch: + what: "A value doesn't fit the column's type — for instance text where a number is expected." + example: "Give a value of the right type: a number for `int`/`real`, a quoted string for `text`, true/false for `bool`." + concept: "Every column has a type, and the database rejects values that don't fit, so a column's data stays consistent and comparable." + not_found: + what: "You named a table or column that doesn't exist." + example: "Check the spelling, or run `show tables` (or `show table `) to see what's there." + concept: "A command can only refer to tables and columns that already exist — create them first if you need them." + already_exists: + what: "You tried to create a table, column, relationship, or index whose name is already taken." + example: "Pick a different name, or drop the existing one first if you meant to replace it." + concept: "Names must be unique within their kind so a command is never ambiguous about what it refers to." + generic: + what: "The database refused the command for the reason shown above." + example: "Read that message for the specifics, adjust the command, and try again." + invalid_value: + what: "A value or option in the command wasn't valid for where it was used." + example: "Check the value against the column's type and the command's accepted options." # Invalid identifier in a schema slot (ADR-0022 stage 8e # + the user's #5). Voice mirrors ADR-0019's "no such # {kind}" wording for consistency with engine errors.