1use std::{error::Error, fmt, io, path::PathBuf};
2
3#[derive(Debug)]
4#[non_exhaustive]
5pub enum BuildError {
6 FtlParse {
7 path: PathBuf,
8 errors: Vec<String>,
10 },
11 FtlRead {
12 path: PathBuf,
13 source: io::Error,
14 },
15 DuplicateKey {
16 key: String,
17 original: PathBuf,
18 original_line: usize,
19 duplicate: PathBuf,
20 duplicate_line: usize,
21 },
22 TermMessageCollision {
27 name: String,
28 term_file: PathBuf,
29 term_line: usize,
30 message_file: PathBuf,
31 message_line: usize,
32 },
33 LocalesFolder {
34 folder: String,
35 source: io::Error,
36 },
37 NoLocaleFolders {
38 folder: String,
39 },
40 DefaultLanguageNotFound {
41 language: String,
42 folder: String,
43 },
44 Lint {
48 messages: Vec<String>,
49 },
50 InvalidContract {
53 messages: Vec<String>,
54 },
55 WriteOutput {
56 path: String,
57 source: io::Error,
58 },
59 Rustfmt(String),
60 Generation(String),
61 Multiple(Vec<BuildError>),
64}
65
66impl BuildError {
67 pub(crate) fn collapse(mut errors: Vec<BuildError>) -> BuildError {
70 if errors.len() == 1 {
71 errors.pop().unwrap()
72 } else {
73 BuildError::Multiple(errors)
74 }
75 }
76}
77
78impl fmt::Display for BuildError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::FtlParse { path, errors } => {
82 write!(f, "Could not parse '{}':", path.display())?;
83 for e in errors {
84 write!(f, "\n {e}")?;
85 }
86 Ok(())
87 }
88 Self::FtlRead { path, .. } => {
89 write!(f, "Could not read '{}'", path.display())
90 }
91 Self::DuplicateKey {
92 key,
93 original,
94 original_line,
95 duplicate,
96 duplicate_line,
97 } => {
98 if original == duplicate {
99 write!(
100 f,
101 "Duplicate message key '{key}' in '{}': lines {original_line} and \
102 {duplicate_line}",
103 duplicate.display(),
104 )
105 } else {
106 write!(
107 f,
108 "Duplicate message key '{key}' in '{}:{duplicate_line}', first defined \
109 in '{}:{original_line}'",
110 duplicate.display(),
111 original.display(),
112 )
113 }
114 }
115 Self::TermMessageCollision {
116 name,
117 term_file,
118 term_line,
119 message_file,
120 message_line,
121 } => {
122 write!(
123 f,
124 "Term '-{name}' and message '{name}' share the same name — \
125 fluent-bundle treats them as the same key and will crash \
126 at runtime. Rename one. Term defined in '{}:{term_line}', \
127 message defined in '{}:{message_line}'.",
128 term_file.display(),
129 message_file.display(),
130 )
131 }
132 Self::LocalesFolder { folder, .. } => {
133 write!(f, "Could not read locales folder '{folder}'")
134 }
135 Self::NoLocaleFolders { folder } => {
136 write!(
137 f,
138 "No locale subfolders found in '{folder}'. Expected \
139 '<lang-id>/<resource>.ftl' files, e.g. 'en/main.ftl'."
140 )
141 }
142 Self::DefaultLanguageNotFound { language, folder } => {
143 write!(
144 f,
145 "Default language '{language}' has no locale subfolder in '{folder}'. \
146 Set it with `BuildOptions::with_default_language`."
147 )
148 }
149 Self::Lint { messages } => {
150 write!(f, "fluent-typed found {} lint error(s):", messages.len())?;
151 for m in messages {
152 write!(f, "\n {m}")?;
153 }
154 Ok(())
155 }
156 Self::InvalidContract { messages } => {
157 write!(
158 f,
159 "fluent-typed found {} invalid message contract(s):",
160 messages.len()
161 )?;
162 for m in messages {
163 write!(f, "\n {m}")?;
164 }
165 Ok(())
166 }
167 Self::WriteOutput { path, .. } => {
168 write!(f, "Could not write file '{path}'")
169 }
170 Self::Rustfmt(msg) => write!(f, "Rustfmt error: {msg}"),
171 Self::Generation(msg) => write!(f, "{msg}"),
172 Self::Multiple(errors) => {
173 write!(f, "{} build errors:", errors.len())?;
174 for e in errors {
175 for (i, line) in e.to_string().lines().enumerate() {
176 if i == 0 {
177 write!(f, "\n - {line}")?;
178 } else {
179 write!(f, "\n {line}")?;
180 }
181 }
182 }
183 Ok(())
184 }
185 }
186 }
187}
188
189impl Error for BuildError {
190 fn source(&self) -> Option<&(dyn Error + 'static)> {
191 match self {
192 Self::FtlRead { source, .. } => Some(source),
193 Self::LocalesFolder { source, .. } => Some(source),
194 Self::WriteOutput { source, .. } => Some(source),
195 _ => None,
196 }
197 }
198}