1use std::path::PathBuf;
2
3use crate::{
4 domain::{LanguageRoot, Locale},
5 fluent::{ParsedFluentFile, QualifiedIdentifier},
6 rust::ParsedRustFile,
7};
8
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
11pub enum Kind {
12 ParseError,
14
15 MissingBase,
18
19 UndefinedBase,
22
23 DuplicateIdentifier,
25
26 InvalidReference,
28
29 MissingTranslation,
31
32 RedundantTranslation,
35
36 SignatureMismatch,
39
40 MalformedIdentifierLiteral,
43
44 UndefinedIdentifierLiteral,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub enum Subject {
55 FluentFile(PathBuf),
57
58 RustFile(PathBuf),
60
61 Locale(Locale),
63
64 Entry(Locale, QualifiedIdentifier),
66
67 LanguageRoot(LanguageRoot),
69}
70
71impl std::fmt::Display for Subject {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 Subject::FluentFile(path_buf) => path_buf.display().fmt(f),
75 Subject::RustFile(path_buf) => path_buf.display().fmt(f),
76 Subject::Locale(locale) => locale.fmt(f),
77 Subject::Entry(locale, qualified_identifier) => {
78 write!(f, "{locale} :: {}", qualified_identifier.to_meta_string())
79 }
80 Subject::LanguageRoot(language_root) => language_root.fmt(f),
81 }
82 }
83}
84
85#[derive(Clone, Debug)]
92pub struct AuditIssue {
93 kind: Kind,
94 subject: Subject,
95 message: String,
96}
97
98impl AuditIssue {
100 fn new(kind: Kind, subject: Subject, message: String) -> Self {
101 Self {
102 kind,
103 subject,
104 message,
105 }
106 }
107
108 pub fn parse_fluent_file_error(file: &ParsedFluentFile) -> Self {
110 Self::new(
111 Kind::ParseError,
112 Subject::FluentFile(file.path().to_path_buf()),
113 file.error_description(),
114 )
115 }
116
117 pub fn parse_rust_file_error(file: &ParsedRustFile) -> Self {
119 Self::new(
120 Kind::ParseError,
121 Subject::FluentFile(file.path().to_path_buf()),
122 file.error_description(),
123 )
124 }
125
126 pub fn missing_base_translation(locale: &Locale) -> Self {
128 Self::new(
129 Kind::MissingBase,
130 Subject::Locale(locale.clone()),
131 format!("no files found for required locale {locale}"),
132 )
133 }
134
135 pub fn undefined_base_locale(root: &LanguageRoot, locales: &[Locale]) -> Self {
137 let locales = locales
138 .iter()
139 .map(|l| l.to_string())
140 .collect::<Vec<_>>()
141 .join(", ");
142
143 Self::new(
144 Kind::UndefinedBase,
145 Subject::LanguageRoot(root.clone()),
146 format!("missing base locale/s for '{locales}'"),
147 )
148 }
149
150 pub fn duplicate_identifier(locale: &Locale, identifier: &QualifiedIdentifier) -> Self {
152 Self::new(
153 Kind::DuplicateIdentifier,
154 Subject::Entry(locale.clone(), identifier.clone()),
155 format!("multiple definitions for '{}'", identifier.to_meta_string()),
156 )
157 }
158
159 pub fn invalid_reference(locale: &Locale, identifier: &QualifiedIdentifier) -> Self {
161 Self::new(
162 Kind::InvalidReference,
163 Subject::Entry(locale.clone(), identifier.clone()),
164 format!("invalid reference '{}'", identifier.to_meta_string()),
165 )
166 }
167
168 pub fn missing_translation(locale: &Locale, identifier: &QualifiedIdentifier) -> Self {
170 Self::new(
171 Kind::MissingTranslation,
172 Subject::Entry(locale.clone(), identifier.clone()),
173 format!("missing translation '{}'", identifier.to_meta_string()),
174 )
175 }
176
177 pub fn redundant_translation(locale: &Locale, identifier: &QualifiedIdentifier) -> Self {
179 Self::new(
180 Kind::RedundantTranslation,
181 Subject::Entry(locale.clone(), identifier.clone()),
182 format!("redundant translation '{}'", identifier.to_meta_string()),
183 )
184 }
185
186 pub fn signature_mismatch(locale: &Locale, identifier: &QualifiedIdentifier) -> Self {
188 Self::new(
189 Kind::SignatureMismatch,
190 Subject::Entry(locale.clone(), identifier.clone()),
191 format!("signature mismatch '{}'", identifier.to_meta_string()),
192 )
193 }
194
195 pub fn undefined_identifier_literal(
197 path: &ParsedRustFile,
198 identifier: &QualifiedIdentifier,
199 ) -> Self {
200 Self::new(
201 Kind::UndefinedIdentifierLiteral,
202 Subject::RustFile(path.path().to_path_buf()),
203 format!(
204 "identifier literal {} is not defined in the canonical document",
205 identifier.to_meta_string()
206 ),
207 )
208 }
209
210 pub fn malformed_identifier_literal(path: &ParsedRustFile, error: &str) -> Self {
212 Self::new(
213 Kind::MalformedIdentifierLiteral,
214 Subject::RustFile(path.path().to_path_buf()),
215 format!("malformed identifier literal: {error}"),
216 )
217 }
218}
219
220impl AuditIssue {
222 pub fn locale(&self) -> Option<Locale> {
224 match &self.subject {
225 Subject::FluentFile(path) => Locale::try_from(path.as_path()).ok(),
226 Subject::RustFile(_) => None,
227 Subject::Locale(locale) => Some(locale.clone()),
228 Subject::Entry(locale, _identifier) => Some(locale.clone()),
229 Subject::LanguageRoot(_) => None,
230 }
231 }
232
233 pub fn message(&self) -> &String {
235 &self.message
236 }
237
238 pub fn subject(&self) -> &Subject {
240 &self.subject
241 }
242
243 pub fn kind(&self) -> &Kind {
245 &self.kind
246 }
247}
248
249impl std::fmt::Display for AuditIssue {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 self.message.fmt(f)
252 }
253}