round-5 follow-up r2: migrate all thiserror Display attributes to catalog
Completes the i18n sweep started in the previous commit. All
remaining hand-rolled user-facing English strings inside
thiserror #[error(...)] attributes have been moved into the
catalog. Drops the thiserror dependency entirely.
Twelve error types migrated:
- dsl::action::UnknownAction → parse.custom.unknown_action
- dsl::parser::ParseError → parse.error_wrapper + parse.empty
- dsl::value::ValueError → value.{type_mismatch,format}
- persistence::csv_io::CsvError → persistence.csv.*
- persistence::mod::PersistenceError → persistence.{io,encode}
- persistence::yaml::YamlError → persistence.yaml.*
- persistence::migrations::MigrateError → persistence.migrate.*
- project::lock::LockError → project.lock.*
- project::naming::NamingError → project.naming.*
- project::naming::UserNameError → project.user_name.*
- project::mod::ProjectError → project.{path_not_found,...}
- project::mod::SafeDeleteError → project.safe_delete.*
- archive::ArchiveError → archive.*
- cli::ArgsError → cli.*
- db::DbError → db.error.*
Pattern per type: drop thiserror::Error derive, write manual
Display calling crate::t!(), keep #[from] semantics via
explicit From impls, override Error::source() where applicable
so #[source]-style chaining is preserved.
Why this matters (user rationale): "fine to have fallbacks for
errors that are purely technical, but lift the output to a
place where it can be localized later and where an adjustment
with friendly text is easily possible if any of them become
part of the happy path." All surface strings now live in
en-US.yaml and can be reworded or localized without touching
Rust source.
Tests: 769 passing, 0 failed, 1 ignored. Clippy clean with
-D warnings. Cargo.toml: drop thiserror = "2.0.18".
This commit is contained in:
+17
-2
@@ -72,13 +72,28 @@ impl fmt::Display for ReferentialAction {
|
||||
}
|
||||
|
||||
/// Error returned when parsing an unknown action keyword.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("unknown referential action '{found}' (expected one of: {expected})")]
|
||||
///
|
||||
/// Display flows through the i18n catalog
|
||||
/// (`parse.custom.unknown_action`) so the wording can be
|
||||
/// localised or adjusted without touching the type.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UnknownAction {
|
||||
pub found: String,
|
||||
pub expected: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UnknownAction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&crate::t!(
|
||||
"parse.custom.unknown_action",
|
||||
found = self.found,
|
||||
expected = self.expected,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UnknownAction {}
|
||||
|
||||
impl FromStr for ReferentialAction {
|
||||
type Err = UnknownAction;
|
||||
|
||||
|
||||
+15
-3
@@ -24,9 +24,8 @@ use crate::dsl::lexer::{LexError, Token, TokenKind, lex};
|
||||
use crate::dsl::types::Type;
|
||||
use crate::dsl::value::Value;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParseError {
|
||||
#[error("could not parse command: {message}")]
|
||||
Invalid {
|
||||
message: String,
|
||||
position: usize,
|
||||
@@ -57,10 +56,23 @@ pub enum ParseError {
|
||||
/// framing).
|
||||
expected: Vec<String>,
|
||||
},
|
||||
#[error("empty input")]
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Invalid { message, .. } => f.write_str(&crate::t!(
|
||||
"parse.error_wrapper",
|
||||
detail = message,
|
||||
)),
|
||||
Self::Empty => f.write_str(&crate::t!("parse.empty")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
impl ParseError {
|
||||
#[must_use]
|
||||
pub const fn position(&self) -> Option<usize> {
|
||||
|
||||
+33
-4
@@ -45,18 +45,47 @@ pub enum Bound {
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
/// Per-column value validation error.
|
||||
///
|
||||
/// Display flows through the i18n catalog (`value.*`); callers
|
||||
/// that format this error get the localised wording for free.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ValueError {
|
||||
#[error("column `{column}` expects {expected_human}, got {got}")]
|
||||
TypeMismatch {
|
||||
column: String,
|
||||
expected_human: String,
|
||||
got: String,
|
||||
},
|
||||
#[error("column `{column}`: {message}")]
|
||||
Format { column: String, message: String },
|
||||
Format {
|
||||
column: String,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ValueError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::TypeMismatch {
|
||||
column,
|
||||
expected_human,
|
||||
got,
|
||||
} => f.write_str(&crate::t!(
|
||||
"value.type_mismatch",
|
||||
column = column,
|
||||
expected_human = expected_human,
|
||||
got = got,
|
||||
)),
|
||||
Self::Format { column, message } => f.write_str(&crate::t!(
|
||||
"value.format",
|
||||
column = column,
|
||||
message = message,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ValueError {}
|
||||
|
||||
impl Value {
|
||||
/// Validate `self` against `column`'s user-facing type and
|
||||
/// produce a value ready for binding.
|
||||
|
||||
Reference in New Issue
Block a user