use rustc_error_messages::{ fluent_bundle::resolver::errors::{ReferenceKind, ResolverError}, FluentArgs, FluentError, }; use std::borrow::Cow; use std::error::Error; use std::fmt; #[derive(Debug)] pub enum TranslateError<'args> { One { id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>, kind: TranslateErrorKind<'args>, }, Two { primary: Box>, fallback: Box>, }, } impl<'args> TranslateError<'args> { pub fn message(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self { Self::One { id, args, kind: TranslateErrorKind::MessageMissing } } pub fn primary(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self { Self::One { id, args, kind: TranslateErrorKind::PrimaryBundleMissing } } pub fn attribute( id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>, attr: &'args str, ) -> Self { Self::One { id, args, kind: TranslateErrorKind::AttributeMissing { attr } } } pub fn value(id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>) -> Self { Self::One { id, args, kind: TranslateErrorKind::ValueMissing } } pub fn fluent( id: &'args Cow<'args, str>, args: &'args FluentArgs<'args>, errs: Vec, ) -> Self { Self::One { id, args, kind: TranslateErrorKind::Fluent { errs } } } pub fn and(self, fallback: TranslateError<'args>) -> TranslateError<'args> { Self::Two { primary: Box::new(self), fallback: Box::new(fallback) } } } #[derive(Debug)] pub enum TranslateErrorKind<'args> { MessageMissing, PrimaryBundleMissing, AttributeMissing { attr: &'args str }, ValueMissing, Fluent { errs: Vec }, } impl fmt::Display for TranslateError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use TranslateErrorKind::*; match self { Self::One { id, args, kind } => { writeln!(f, "failed while formatting fluent string `{id}`: ")?; match kind { MessageMissing => writeln!(f, "message was missing")?, PrimaryBundleMissing => writeln!(f, "the primary bundle was missing")?, AttributeMissing { attr } => { writeln!(f, "the attribute `{attr}` was missing")?; writeln!(f, "help: add `.{attr} = `")?; } ValueMissing => writeln!(f, "the value was missing")?, Fluent { errs } => { for err in errs { match err { FluentError::ResolverError(ResolverError::Reference( ReferenceKind::Message { id, .. } | ReferenceKind::Variable { id, .. }, )) => { if args.iter().any(|(arg_id, _)| arg_id == id) { writeln!( f, "argument `{id}` exists but was not referenced correctly" )?; writeln!(f, "help: try using `{{${id}}}` instead")?; } else { writeln!( f, "the fluent string has an argument `{id}` that was not found." )?; let vars: Vec<&str> = args.iter().map(|(a, _v)| a).collect(); match &*vars { [] => writeln!(f, "help: no arguments are available")?, [one] => writeln!( f, "help: the argument `{one}` is available" )?, [first, middle @ .., last] => { write!(f, "help: the arguments `{first}`")?; for a in middle { write!(f, ", `{a}`")?; } writeln!(f, " and `{last}` are available")?; } } } } _ => writeln!(f, "{err}")?, } } } } } // If someone cares about primary bundles, they'll probably notice it's missing // regardless or will be using `debug_assertions` // so we skip the arm below this one to avoid confusing the regular user. Self::Two { primary: box Self::One { kind: PrimaryBundleMissing, .. }, fallback } => { fmt::Display::fmt(fallback, f)?; } Self::Two { primary, fallback } => { writeln!( f, "first, fluent formatting using the primary bundle failed:\n {primary}\n \ while attempting to recover by using the fallback bundle instead, another error occurred:\n{fallback}" )?; } } Ok(()) } } impl Error for TranslateError<'_> {}