Merged all of the Fluent errors into the fluent::Error module, and added error handling for:
en-US)2SITVDYW6KANM24QXRHVSBL6S77UHKJLOSOHSUZQBJFL5NAAGQYAC QFPQZR4K4UZ7R2GQZJG4NYBGVQJVL2ANIKGGTOHAMIRIBQHPSQGAC F5LG7WENUUDRSCTDMA4M6BAC5RWTGQO45C4ZEBZDX6FHCTTHBVGQC O77KA6C4UJGZXVGPEA7WCRQH6XYQJPWETSPDXI3VOKOSRQND7JEQC NO3PDO7PY7J3WPADNCS5VD6HKFY63E23I3SDR4DHXNVQJTG27RAAC 5TEX4MNUC4LDDRMNEOVCFNUUEZAGUXMKO3OIEQFXWRQKXSHY2NRQC ROSR4HD5ENPQU3HH5IVYSOA5YM72W77CHVQARSD3T67BUNYG7KZQC 2XQ6ZB4WZNNR4KNC3VWNTV7IRMGGAEP33JPQUVB3CVWAKHECZVRQC SHNZZSZGIBTTD4IV5SMW5BIN5DORUWQVTVTNB5RMRD5CTFNOMJ6AC InvalidReference(#[from] ast::InvalidReference),
#[error("the reference `${fluent_name}` doesn't match a Rust field `{rust_name}`")]InvalidReference {fluent_name: String,rust_name: String,#[source_code]source_code: NamedSource<String>,#[label("This references `{rust_name}` which doesn't exist")]span: SourceSpan,#[help]valid_references: String,},#[error("missing canonical locale `{canonical_locale}`")]MissingCanonicalLocale {canonical_locale: String,#[help]matched_locales: String,},#[error(r#"message "{unexpected_key}" from `{locale}` is not in the canonical `{canonical_locale}` locale"#)]#[help("the canonical locale must include all keys!")]UnexpectedKey {unexpected_key: String,locale: String,canonical_locale: String,#[source_code]source_code: NamedSource<String>,#[label("This key isn't in the `{canonical_locale}` locale")]span: SourceSpan,},#[error("unable to parse Fluent source code")]ParserErrors {#[source_code]source_code: NamedSource<String>,#[related]related: Vec<ParserError>,},
// TODO: return an error instead of paniclet syntax_tree = fluent_syntax::parser::parse(file_contents).unwrap();
let syntax_tree = match fluent_syntax::parser::parse(file_contents.clone()) {Ok(syntax_tree) => syntax_tree,Err((_partial_syntax_tree, parser_errors)) => {// Map the `fluent_syntax` errors to `miette::Diagnostic` errorslet related = parser_errors.into_iter().map(|error| ParserError {span: SourceSpan::from(error.pos),kind: error.kind,}).collect();return Err(Error::ParserErrors {source_code: NamedSource::new(path.to_string_lossy(), file_contents),related,});}};
}/// Calculate the byte offset of the serializedfn source_with_message_offset(&self, id: &str) -> (NamedSource<String>, usize) {// Find the message position in the ASTlet ast_index = self.syntax_tree.body.iter().position(|entry| {if let Entry::Message(message) = entry {message.id.name == id} else {false}}).unwrap();let options = fluent_syntax::serializer::Options {// Make sure to include all source code in error snippet, even if marked as "junk"with_junk: true,};// Serialize everything before this message to get the byte offsetlet source_before_message = fluent_syntax::serializer::serialize_with_options(&Resource {body: self.syntax_tree.body[0..ast_index].to_vec(),},options,);let byte_offset = source_before_message.len();let source_after_offset = fluent_syntax::serializer::serialize_with_options(&Resource {body: self.syntax_tree.body[ast_index..].to_vec(),},options,);let source = format!("{source_before_message}{source_after_offset}");let named_source = NamedSource::new(self.path.to_string_lossy(), source);(named_source, byte_offset)
// Collect all keys used in the Fluent source codelet keys: HashSet<&str> =HashSet::from_iter(locales.values().flat_map(|resource| resource.message_ids()));// Collect all keys from the canonical resource// TODO: return an error instead of paniclet canonical_resource = locales.get(&canonical_locale).unwrap();// TODO: return an error instead of panic
// Make sure the canonical locale existslet canonical_resource = if let Some(canonical_resource) = locales.get(&canonical_locale) {canonical_resource} else {return Err(Error::MissingCanonicalLocale {canonical_locale: canonical_locale.to_string(),matched_locales: format!("the following locales were found:\n{}",locales.keys().map(|locale| format!("- {locale}")).collect::<Vec<_>>().join("\n")),});};
assert_eq!(keys, HashSet::from_iter(canonical_resource.message_ids()));
let canonical_keys: Vec<&str> = canonical_resource.message_ids().collect();for (locale, source_file) in &locales {// Find any keys in `locale_keys` missing from `canonical_keys`let mut unexpected_keys = source_file.message_ids().filter(|id: &&str| !canonical_keys.contains(id));if let Some(unexpected_key) = unexpected_keys.next() {let (src, message_offset) = source_file.source_with_message_offset(unexpected_key);return Err(Error::UnexpectedKey {unexpected_key: unexpected_key.to_string(),locale: locale.to_string(),canonical_locale: canonical_locale.to_string(),source_code: src,span: SourceSpan::new(message_offset.into(), unexpected_key.len()),});}}
// Serialize the Fluent file AST back into a Stringlet source_string = fluent_syntax::serializer::serialize_with_options(&message_context.syntax_tree,fluent_syntax::serializer::Options {// Make sure to include all source code in error snippet, even if marked as "junk"with_junk: true,},);
let (source_code, offset) = message_context.source.source_with_message_offset(message_context.root_id);
return Err(Error::InvalidReference(InvalidReference {src: NamedSource::new(message_context.path, source_string.clone()),span: (location..location + id.name.len()).into(),help: format!(
return Err(Error::InvalidReference {fluent_name: id.name.clone(),rust_name: id.name.to_snake_case(),source_code,span: SourceSpan::new(location.into(), id.name.len()),valid_references: format!(