style: format the whole tree with cargo fmt (stock defaults, #35)
One-time, mechanical reformat — no functional changes. The tree was not rustfmt-clean (~1800 hunks across ~100 files); this brings it to stock `cargo fmt` defaults so a `cargo fmt --check` CI gate can follow. Behaviour-preserving: 2509 pass / 0 fail / 1 ignored (unchanged baseline), clippy clean. A .git-blame-ignore-revs entry follows so `git blame` skips this commit.
This commit is contained in:
+3
-11
@@ -43,17 +43,11 @@ impl Catalog {
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten(
|
||||
value: &serde_norway::Value,
|
||||
prefix: String,
|
||||
out: &mut HashMap<String, String>,
|
||||
) {
|
||||
fn flatten(value: &serde_norway::Value, prefix: String, out: &mut HashMap<String, String>) {
|
||||
match value {
|
||||
serde_norway::Value::Mapping(map) => {
|
||||
for (k, v) in map {
|
||||
let k_str = k
|
||||
.as_str()
|
||||
.expect("catalog keys must be strings");
|
||||
let k_str = k.as_str().expect("catalog keys must be strings");
|
||||
let next = if prefix.is_empty() {
|
||||
k_str.to_string()
|
||||
} else {
|
||||
@@ -85,9 +79,7 @@ pub fn catalog() -> &'static Catalog {
|
||||
/// See module docs for failure modes.
|
||||
pub fn translate(key: &str, args: &[(&str, &dyn Display)]) -> String {
|
||||
let template = catalog().get(key).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"missing catalog key: `{key}` (the validator should have caught this)"
|
||||
);
|
||||
panic!("missing catalog key: `{key}` (the validator should have caught this)");
|
||||
});
|
||||
substitute(template, args, key)
|
||||
}
|
||||
|
||||
+24
-29
@@ -41,8 +41,14 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("diagnostic.alias_used_as_column", &["name"]),
|
||||
("diagnostic.ambiguous_column", &["column", "qualifiers"]),
|
||||
("diagnostic.auto_column_overridden", &["column", "type"]),
|
||||
("diagnostic.compound_arity_mismatch", &["op", "left_n", "right_n"]),
|
||||
("diagnostic.cte_arity_mismatch", &["cte", "declared", "actual"]),
|
||||
(
|
||||
"diagnostic.compound_arity_mismatch",
|
||||
&["op", "left_n", "right_n"],
|
||||
),
|
||||
(
|
||||
"diagnostic.cte_arity_mismatch",
|
||||
&["cte", "declared", "actual"],
|
||||
),
|
||||
("diagnostic.duplicate_cte", &["name"]),
|
||||
("diagnostic.eq_null", &[]),
|
||||
("diagnostic.insert_arity_mismatch", &["expected", "actual"]),
|
||||
@@ -63,7 +69,10 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
),
|
||||
("diagnostic.not_null_missing", &["column"]),
|
||||
("diagnostic.like_numeric", &["column", "type"]),
|
||||
("diagnostic.projection_alias_misplaced", &["alias", "clause"]),
|
||||
(
|
||||
"diagnostic.projection_alias_misplaced",
|
||||
&["alias", "clause"],
|
||||
),
|
||||
("diagnostic.table_used_as_column", &["name"]),
|
||||
("diagnostic.type_mismatch", &["column", "type"]),
|
||||
("diagnostic.unknown_column", &["name", "table"]),
|
||||
@@ -149,10 +158,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
"error.type_mismatch.change_column.headline",
|
||||
&["table", "column", "src_type", "target_type"],
|
||||
),
|
||||
(
|
||||
"error.type_mismatch.change_column.hint",
|
||||
&["target_type"],
|
||||
),
|
||||
("error.type_mismatch.change_column.hint", &["target_type"]),
|
||||
(
|
||||
"error.type_mismatch.insert.headline",
|
||||
&["value", "expected_type"],
|
||||
@@ -219,10 +225,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("help.data.explain", &[]),
|
||||
// ---- Hint panel ambient typing assistance (ADR-0022 §6) ----
|
||||
("hint.ambient_complete", &[]),
|
||||
(
|
||||
"hint.ambient_error_with_usage",
|
||||
&["message", "usage"],
|
||||
),
|
||||
("hint.ambient_error_with_usage", &["message", "usage"]),
|
||||
("hint.ambient_expected", &["expected"]),
|
||||
("hint.getting_started", &[]),
|
||||
("hint.block.heading", &[]),
|
||||
@@ -404,10 +407,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("hint.cmd.explain_sql.what", &[]),
|
||||
("hint.cmd.explain_sql.example", &[]),
|
||||
("hint.cmd.explain_sql.concept", &[]),
|
||||
(
|
||||
"hint.ambient_invalid_ident",
|
||||
&["kind", "found"],
|
||||
),
|
||||
("hint.ambient_invalid_ident", &["kind", "found"]),
|
||||
("hint.ambient_typing_name", &[]),
|
||||
// Issue #4: introduce the advanced-mode CREATE TABLE element
|
||||
// slot (`create table T (`) so the otherwise-invisible
|
||||
@@ -415,10 +415,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("hint.create_table_element", &[]),
|
||||
("hint.seed_count", &[]),
|
||||
("hint.value_literal_slot", &[]),
|
||||
(
|
||||
"hint.ambient_typing_name_then",
|
||||
&["next"],
|
||||
),
|
||||
("hint.ambient_typing_name_then", &["next"]),
|
||||
// Per-column-type value-slot hints (ADR-0024 §Phase D).
|
||||
("hint.value_slot_blob", &[]),
|
||||
("hint.value_slot_bool", &[]),
|
||||
@@ -441,7 +438,10 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("parse.custom.alter_named_unique", &[]),
|
||||
("parse.custom.bind_type_mismatch", &["found", "expected"]),
|
||||
("parse.custom.change_column_flags_exclusive", &[]),
|
||||
("parse.custom.constraint_redundant_on_pk", &["column", "constraint"]),
|
||||
(
|
||||
"parse.custom.constraint_redundant_on_pk",
|
||||
&["column", "constraint"],
|
||||
),
|
||||
("parse.custom.create_table_needs_pk", &[]),
|
||||
("parse.custom.expression_too_deep", &[]),
|
||||
("parse.custom.insert_form_a_missing_values", &["columns"]),
|
||||
@@ -576,10 +576,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
&["table", "col_count", "col_list", "supplied", "non_auto_csv"],
|
||||
),
|
||||
("select.internal_table", &["table"]),
|
||||
(
|
||||
"cli.invalid_value",
|
||||
&["flag", "value", "expected"],
|
||||
),
|
||||
("cli.invalid_value", &["flag", "value", "expected"]),
|
||||
("cli.missing_value", &["flag"]),
|
||||
("cli.multiple_paths", &["first", "second"]),
|
||||
("cli.resume_with_path", &[]),
|
||||
@@ -867,8 +864,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let declared: HashSet<&str> =
|
||||
KEYS_AND_PLACEHOLDERS.iter().map(|(k, _)| *k).collect();
|
||||
let declared: HashSet<&str> = KEYS_AND_PLACEHOLDERS.iter().map(|(k, _)| *k).collect();
|
||||
for key in cat.keys() {
|
||||
if key.starts_with("_test.") {
|
||||
continue;
|
||||
@@ -890,9 +886,8 @@ mod tests {
|
||||
/// Mirror of `tests/engine_vocabulary_audit.rs::FORBIDDEN`,
|
||||
/// duplicated here so the catalog validator is self-contained
|
||||
/// (no dependency on the integration-test binary).
|
||||
const FORBIDDEN_ENGINE_VOCABULARY: &[&str] = &[
|
||||
"SQLite", "sqlite", "rusqlite", "STRICT", "PRAGMA",
|
||||
];
|
||||
const FORBIDDEN_ENGINE_VOCABULARY: &[&str] =
|
||||
&["SQLite", "sqlite", "rusqlite", "STRICT", "PRAGMA"];
|
||||
|
||||
/// Detect a `{name:...}` format-specifier placeholder.
|
||||
/// Doubled braces `{{` / `}}` are escapes — must skip them.
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ pub mod keys;
|
||||
pub mod translate;
|
||||
|
||||
pub use error::{DiagnosticTable, FriendlyError};
|
||||
pub use format::{catalog, Catalog};
|
||||
pub use translate::{error_hint_class, FailureContext, Operation, TranslateContext, Verbosity};
|
||||
pub use format::{Catalog, catalog};
|
||||
pub use translate::{FailureContext, Operation, TranslateContext, Verbosity, error_hint_class};
|
||||
|
||||
// `translate::translate` and `format::translate` are different
|
||||
// callables — the former is the structured DbError → FriendlyError
|
||||
|
||||
+79
-76
@@ -201,11 +201,7 @@ impl TranslateContext {
|
||||
/// Combine schema-resolved facts with operation and
|
||||
/// verbosity to build the full translator input.
|
||||
#[must_use]
|
||||
pub fn from_facts(
|
||||
operation: Operation,
|
||||
verbosity: Verbosity,
|
||||
facts: FailureContext,
|
||||
) -> Self {
|
||||
pub fn from_facts(operation: Operation, verbosity: Verbosity, facts: FailureContext) -> Self {
|
||||
Self {
|
||||
operation: Some(operation),
|
||||
table: facts.table,
|
||||
@@ -234,15 +230,15 @@ pub fn translate(error: &DbError, ctx: &TranslateContext) -> FriendlyError {
|
||||
// refusal sites). Catalog entries exist for the typed
|
||||
// invalid-value cases but the migration sweep
|
||||
// (ADR-0019 §9) is what wires them. For now, passthrough.
|
||||
DbError::Unsupported(message) | DbError::InvalidValue(message) => {
|
||||
passthrough(message)
|
||||
}
|
||||
DbError::Unsupported(message) | DbError::InvalidValue(message) => passthrough(message),
|
||||
DbError::PersistenceFatal { message, .. }
|
||||
| DbError::RebuildRowFailed { detail: message, .. }
|
||||
| DbError::RebuildRowFailed {
|
||||
detail: message, ..
|
||||
}
|
||||
| DbError::Io(message) => passthrough(message),
|
||||
DbError::WorkerGone => passthrough(
|
||||
"the database worker is no longer available — the application must restart",
|
||||
),
|
||||
DbError::WorkerGone => {
|
||||
passthrough("the database worker is no longer available — the application must restart")
|
||||
}
|
||||
};
|
||||
// Attach the row pinpoint when the runtime resolved one.
|
||||
// The translator never builds the table itself — it only
|
||||
@@ -320,11 +316,7 @@ const fn fk_hint_class(ctx: &TranslateContext) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_sqlite(
|
||||
message: &str,
|
||||
kind: SqliteErrorKind,
|
||||
ctx: &TranslateContext,
|
||||
) -> FriendlyError {
|
||||
fn translate_sqlite(message: &str, kind: SqliteErrorKind, ctx: &TranslateContext) -> FriendlyError {
|
||||
// `change column ... --dont-convert` lets the engine
|
||||
// accept or refuse each cell. Whatever the engine returns
|
||||
// (constraint, datatype mismatch, …) means "the new type
|
||||
@@ -392,8 +384,8 @@ fn translate_constraint(message: &str, ctx: &TranslateContext) -> FriendlyError
|
||||
// ---- UNIQUE -----------------------------------------------------
|
||||
|
||||
fn translate_unique(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
let (table, column) = parse_qualified_target(message)
|
||||
.unwrap_or_else(|| (ctx_table(ctx), ctx_column(ctx)));
|
||||
let (table, column) =
|
||||
parse_qualified_target(message).unwrap_or_else(|| (ctx_table(ctx), ctx_column(ctx)));
|
||||
let value = ctx_value(ctx);
|
||||
match ctx.operation {
|
||||
Some(Operation::Update) => fe(
|
||||
@@ -405,11 +397,7 @@ fn translate_unique(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
),
|
||||
verbose_hint(
|
||||
ctx,
|
||||
t!(
|
||||
"error.unique.update.hint",
|
||||
table = table,
|
||||
column = column
|
||||
),
|
||||
t!("error.unique.update.hint", table = table, column = column),
|
||||
),
|
||||
),
|
||||
// Default to the INSERT variant — it's the most common
|
||||
@@ -425,11 +413,7 @@ fn translate_unique(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
),
|
||||
verbose_hint(
|
||||
ctx,
|
||||
t!(
|
||||
"error.unique.insert.hint",
|
||||
table = table,
|
||||
column = column
|
||||
),
|
||||
t!("error.unique.insert.hint", table = table, column = column),
|
||||
),
|
||||
),
|
||||
}
|
||||
@@ -542,8 +526,8 @@ fn fk_parent_side_update(ctx: &TranslateContext) -> FriendlyError {
|
||||
// ---- NOT NULL --------------------------------------------------
|
||||
|
||||
fn translate_not_null(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
let (table, column) = parse_qualified_target(message)
|
||||
.unwrap_or_else(|| (ctx_table(ctx), ctx_column(ctx)));
|
||||
let (table, column) =
|
||||
parse_qualified_target(message).unwrap_or_else(|| (ctx_table(ctx), ctx_column(ctx)));
|
||||
match ctx.operation {
|
||||
Some(Operation::Update) => fe(
|
||||
t!(
|
||||
@@ -576,9 +560,17 @@ fn translate_check(_message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
let column = ctx_column(ctx);
|
||||
let is_update = matches!(ctx.operation, Some(Operation::Update));
|
||||
let headline = if is_update {
|
||||
t!("error.check.update.headline", table = table, column = column)
|
||||
t!(
|
||||
"error.check.update.headline",
|
||||
table = table,
|
||||
column = column
|
||||
)
|
||||
} else {
|
||||
t!("error.check.insert.headline", table = table, column = column)
|
||||
t!(
|
||||
"error.check.insert.headline",
|
||||
table = table,
|
||||
column = column
|
||||
)
|
||||
};
|
||||
let hint = ctx.check_rule.as_ref().map_or_else(
|
||||
|| {
|
||||
@@ -613,8 +605,7 @@ fn translate_check(_message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
// ---- not_found / already_exists --------------------------------
|
||||
|
||||
fn translate_not_found_table(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
let name = parse_after_colon(message)
|
||||
.map_or_else(|| ctx_table(ctx), str::to_string);
|
||||
let name = parse_after_colon(message).map_or_else(|| ctx_table(ctx), str::to_string);
|
||||
headline_only(t!("error.not_found.table.headline", name = name))
|
||||
}
|
||||
|
||||
@@ -656,17 +647,11 @@ fn translate_already_exists(message: &str, ctx: &TranslateContext) -> FriendlyEr
|
||||
column = column
|
||||
));
|
||||
}
|
||||
return headline_only(t!(
|
||||
"error.already_exists.table.headline",
|
||||
name = name
|
||||
));
|
||||
return headline_only(t!("error.already_exists.table.headline", name = name));
|
||||
}
|
||||
// No backticks — engine-style "table T already exists".
|
||||
if let Some(name) = parse_after_word(message, "table") {
|
||||
return headline_only(t!(
|
||||
"error.already_exists.table.headline",
|
||||
name = name
|
||||
));
|
||||
return headline_only(t!("error.already_exists.table.headline", name = name));
|
||||
}
|
||||
if let Some(name) = parse_after_word(message, "relationship") {
|
||||
return headline_only(t!(
|
||||
@@ -696,36 +681,25 @@ fn translate_generic(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
if lower.contains("misuse of aggregate") {
|
||||
return headline_only(t!("engine.aggregate_misuse", name = "?"));
|
||||
}
|
||||
if lower.contains("group by")
|
||||
|| lower.contains("must appear in")
|
||||
{
|
||||
if lower.contains("group by") || lower.contains("must appear in") {
|
||||
return headline_only(t!("engine.group_by_required"));
|
||||
}
|
||||
if (lower.contains("union")
|
||||
|| lower.contains("intersect")
|
||||
|| lower.contains("except"))
|
||||
if (lower.contains("union") || lower.contains("intersect") || lower.contains("except"))
|
||||
&& lower.contains("result columns")
|
||||
{
|
||||
// Last-resort safety net — the pre-flight pass in 2d.1
|
||||
// catches this in most cases; if the engine surfaces it
|
||||
// anyway, route it through the engine-neutral key.
|
||||
return headline_only(t!(
|
||||
"engine.compound_arity_mismatch",
|
||||
op = "set operator"
|
||||
));
|
||||
return headline_only(t!("engine.compound_arity_mismatch", op = "set operator"));
|
||||
}
|
||||
if lower.contains("scalar subquery") || lower.contains("more than one row") {
|
||||
return headline_only(t!("engine.scalar_subquery_too_many_rows"));
|
||||
}
|
||||
if lower.contains("recursive")
|
||||
&& (lower.contains("cte") || lower.contains("union"))
|
||||
{
|
||||
if lower.contains("recursive") && (lower.contains("cte") || lower.contains("union")) {
|
||||
return headline_only(t!("engine.recursive_cte_malformed"));
|
||||
}
|
||||
|
||||
let operation = ctx
|
||||
.operation
|
||||
.map_or("operation", Operation::keyword);
|
||||
let operation = ctx.operation.map_or("operation", Operation::keyword);
|
||||
// F2 (ADR-0035 Amendment 1): when no table is in context, use the
|
||||
// table-less hint so a contextless `friendly_message()` (replay, undo,
|
||||
// rebuild, export) never renders a literal `{table}` placeholder.
|
||||
@@ -789,23 +763,33 @@ fn ctx_table(ctx: &TranslateContext) -> String {
|
||||
}
|
||||
|
||||
fn ctx_column(ctx: &TranslateContext) -> String {
|
||||
ctx.column.clone().unwrap_or_else(|| "the column".to_string())
|
||||
ctx.column
|
||||
.clone()
|
||||
.unwrap_or_else(|| "the column".to_string())
|
||||
}
|
||||
|
||||
fn ctx_value(ctx: &TranslateContext) -> String {
|
||||
ctx.value.clone().unwrap_or_else(|| "that value".to_string())
|
||||
ctx.value
|
||||
.clone()
|
||||
.unwrap_or_else(|| "that value".to_string())
|
||||
}
|
||||
|
||||
fn ctx_parent_table(ctx: &TranslateContext) -> String {
|
||||
ctx.parent_table.clone().unwrap_or_else(|| "the referenced table".to_string())
|
||||
ctx.parent_table
|
||||
.clone()
|
||||
.unwrap_or_else(|| "the referenced table".to_string())
|
||||
}
|
||||
|
||||
fn ctx_parent_column(ctx: &TranslateContext) -> String {
|
||||
ctx.parent_column.clone().unwrap_or_else(|| "the referenced column".to_string())
|
||||
ctx.parent_column
|
||||
.clone()
|
||||
.unwrap_or_else(|| "the referenced column".to_string())
|
||||
}
|
||||
|
||||
fn ctx_child_table(ctx: &TranslateContext) -> String {
|
||||
ctx.child_table.clone().unwrap_or_else(|| "the referencing table".to_string())
|
||||
ctx.child_table
|
||||
.clone()
|
||||
.unwrap_or_else(|| "the referencing table".to_string())
|
||||
}
|
||||
|
||||
/// Extract `T.col` from a message like
|
||||
@@ -847,11 +831,7 @@ fn parse_after_word<'a>(message: &'a str, keyword: &str) -> Option<&'a str> {
|
||||
let rest = message[pos..].trim_start();
|
||||
let token_end = rest.find(|c: char| c.is_whitespace()).unwrap_or(rest.len());
|
||||
let token = rest[..token_end].trim_matches(|c: char| c == '`' || c == '\'');
|
||||
if token.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(token)
|
||||
}
|
||||
if token.is_empty() { None } else { Some(token) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -876,15 +856,24 @@ mod tests {
|
||||
};
|
||||
let d = TranslateContext::default;
|
||||
assert_eq!(
|
||||
error_hint_class(&sqlite(SqliteErrorKind::NoSuchTable, "no such table: X"), &d()),
|
||||
error_hint_class(
|
||||
&sqlite(SqliteErrorKind::NoSuchTable, "no such table: X"),
|
||||
&d()
|
||||
),
|
||||
Some("not_found")
|
||||
);
|
||||
assert_eq!(
|
||||
error_hint_class(&sqlite(SqliteErrorKind::NoSuchColumn, "no such column: X"), &d()),
|
||||
error_hint_class(
|
||||
&sqlite(SqliteErrorKind::NoSuchColumn, "no such column: X"),
|
||||
&d()
|
||||
),
|
||||
Some("not_found")
|
||||
);
|
||||
assert_eq!(
|
||||
error_hint_class(&sqlite(SqliteErrorKind::AlreadyExists, "already exists"), &d()),
|
||||
error_hint_class(
|
||||
&sqlite(SqliteErrorKind::AlreadyExists, "already exists"),
|
||||
&d()
|
||||
),
|
||||
Some("already_exists")
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -933,13 +922,19 @@ mod tests {
|
||||
parent_table: Some("Parent".to_string()),
|
||||
..TranslateContext::default()
|
||||
};
|
||||
assert_eq!(error_hint_class(&fk(), &ctx), Some("foreign_key.child_side"));
|
||||
assert_eq!(
|
||||
error_hint_class(&fk(), &ctx),
|
||||
Some("foreign_key.child_side")
|
||||
);
|
||||
// child_table populated → parent-side.
|
||||
let ctx = TranslateContext {
|
||||
child_table: Some("Child".to_string()),
|
||||
..TranslateContext::default()
|
||||
};
|
||||
assert_eq!(error_hint_class(&fk(), &ctx), Some("foreign_key.parent_side"));
|
||||
assert_eq!(
|
||||
error_hint_class(&fk(), &ctx),
|
||||
Some("foreign_key.parent_side")
|
||||
);
|
||||
// No enrichment: operation is the tiebreaker.
|
||||
assert_eq!(
|
||||
error_hint_class(&fk(), &ctx_with(Operation::Delete)),
|
||||
@@ -1049,14 +1044,22 @@ mod tests {
|
||||
ctx.parent_column = Some("country, code".to_string());
|
||||
ctx.value = Some("7, 8".to_string());
|
||||
let f = translate(&err, &ctx);
|
||||
assert!(f.headline.contains("no parent row"), "child-side: {}", f.headline);
|
||||
assert!(
|
||||
f.headline.contains("no parent row"),
|
||||
"child-side: {}",
|
||||
f.headline
|
||||
);
|
||||
assert!(f.headline.contains("Region"));
|
||||
assert!(
|
||||
f.headline.contains("country, code"),
|
||||
"both parent columns must appear: {}",
|
||||
f.headline
|
||||
);
|
||||
assert!(f.headline.contains("`7, 8`"), "joined value: {}", f.headline);
|
||||
assert!(
|
||||
f.headline.contains("`7, 8`"),
|
||||
"joined value: {}",
|
||||
f.headline
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user