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 WriteOutput {
51 path: String,
52 source: io::Error,
53 },
54 Rustfmt(String),
55 Generation(String),
56 Multiple(Vec<BuildError>),
59}
60
61impl BuildError {
62 pub(crate) fn collapse(mut errors: Vec<BuildError>) -> BuildError {
65 if errors.len() == 1 {
66 errors.pop().unwrap()
67 } else {
68 BuildError::Multiple(errors)
69 }
70 }
71}
72
73impl fmt::Display for BuildError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::FtlParse { path, errors } => {
77 write!(f, "Could not parse '{}':", path.display())?;
78 for e in errors {
79 write!(f, "\n {e}")?;
80 }
81 Ok(())
82 }
83 Self::FtlRead { path, .. } => {
84 write!(f, "Could not read '{}'", path.display())
85 }
86 Self::DuplicateKey {
87 key,
88 original,
89 original_line,
90 duplicate,
91 duplicate_line,
92 } => {
93 if original == duplicate {
94 write!(
95 f,
96 "Duplicate message key '{key}' in '{}': lines {original_line} and \
97 {duplicate_line}",
98 duplicate.display(),
99 )
100 } else {
101 write!(
102 f,
103 "Duplicate message key '{key}' in '{}:{duplicate_line}', first defined \
104 in '{}:{original_line}'",
105 duplicate.display(),
106 original.display(),
107 )
108 }
109 }
110 Self::TermMessageCollision {
111 name,
112 term_file,
113 term_line,
114 message_file,
115 message_line,
116 } => {
117 write!(
118 f,
119 "Term '-{name}' and message '{name}' share the same name — \
120 fluent-bundle treats them as the same key and will crash \
121 at runtime. Rename one. Term defined in '{}:{term_line}', \
122 message defined in '{}:{message_line}'.",
123 term_file.display(),
124 message_file.display(),
125 )
126 }
127 Self::LocalesFolder { folder, .. } => {
128 write!(f, "Could not read locales folder '{folder}'")
129 }
130 Self::NoLocaleFolders { folder } => {
131 write!(
132 f,
133 "No locale subfolders found in '{folder}'. Expected \
134 '<lang-id>/<resource>.ftl' files, e.g. 'en/main.ftl'."
135 )
136 }
137 Self::DefaultLanguageNotFound { language, folder } => {
138 write!(
139 f,
140 "Default language '{language}' has no locale subfolder in '{folder}'. \
141 Set it with `BuildOptions::with_default_language`."
142 )
143 }
144 Self::Lint { messages } => {
145 write!(f, "fluent-typed found {} lint error(s):", messages.len())?;
146 for m in messages {
147 write!(f, "\n {m}")?;
148 }
149 Ok(())
150 }
151 Self::WriteOutput { path, .. } => {
152 write!(f, "Could not write file '{path}'")
153 }
154 Self::Rustfmt(msg) => write!(f, "Rustfmt error: {msg}"),
155 Self::Generation(msg) => write!(f, "{msg}"),
156 Self::Multiple(errors) => {
157 write!(f, "{} build errors:", errors.len())?;
158 for e in errors {
159 for (i, line) in e.to_string().lines().enumerate() {
160 if i == 0 {
161 write!(f, "\n - {line}")?;
162 } else {
163 write!(f, "\n {line}")?;
164 }
165 }
166 }
167 Ok(())
168 }
169 }
170 }
171}
172
173impl Error for BuildError {
174 fn source(&self) -> Option<&(dyn Error + 'static)> {
175 match self {
176 Self::FtlRead { source, .. } => Some(source),
177 Self::LocalesFolder { source, .. } => Some(source),
178 Self::WriteOutput { source, .. } => Some(source),
179 _ => None,
180 }
181 }
182}