compatdb_core/error.rs
1//! What can be wrong with one field of a rule, and with a line of
2//! [`ENV_VAR`].
3
4use std::fmt;
5
6use crate::{ENV_VAR, Key};
7#[cfg(doc)]
8use crate::{Rule, Table, retired_env};
9
10/// Why a field of a rule cannot be read, or a value cannot be set.
11///
12/// The [`Display`](fmt::Display) form is a short phrase, for example
13/// `unknown key "bogus"`, which `compatdb.so` logs after `ignoring`.
14#[non_exhaustive]
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum FieldError {
17 /// A key the format does not know, as it was written.
18 UnknownKey(String),
19 /// A value that `key` does not take.
20 ///
21 /// From a field of a rule and from
22 /// [`Scalar::parse_value`](crate::Scalar::parse_value), `expected` is
23 /// [`Key::values`], `default` included. From the `FromStr` of a value
24 /// type such as [`Dxgi`](crate::Dxgi), it lists that type's values
25 /// alone, since `default` is none of them.
26 BadValue {
27 /// The key whose value it is.
28 key: Key,
29 /// The value as it was written, decoded.
30 value: String,
31 /// The spellings the value can have there, in lowercase.
32 expected: &'static [&'static str],
33 },
34 /// A NUL in a value, which the process APIs that take it would end the
35 /// text at.
36 Nul,
37 /// An `exe` that is empty, or a rule without one, which matches no
38 /// process.
39 MissingExe,
40 /// An empty argument, which would append nothing.
41 EmptyArgument,
42 /// An empty `debug_channels` entry, which names no channel.
43 EmptyDebugChannel,
44 /// A `debug_channels` entry with a `,`, as it was written, which
45 /// `WINEDEBUG` would read as more than one entry.
46 CommaInDebugChannel(String),
47 /// A variable name that is empty or holds a `=`, as it was written.
48 InvalidEnvName(String),
49 /// A DLL override without the `=` between its modules and its load
50 /// order, as it was written.
51 InvalidDllOverride(String),
52 /// A `%` in a value that two hex digits do not follow.
53 BadEscape,
54 /// A value whose escapes decode to bytes that are not UTF-8.
55 InvalidUtf8,
56 /// A field without the `=` between its key and its value.
57 MissingEquals,
58 /// A second field for a key that holds one value: a matcher or a scalar
59 /// setting.
60 Duplicate(Key),
61 /// A key that the settings of a launcher may still hold but that is no
62 /// longer read (see [`Key::from_setting`]).
63 RetiredKey {
64 /// The key as it was written.
65 key: String,
66 /// Why it is no longer read and what to do instead, a clause that
67 /// follows the key.
68 reason: &'static str,
69 /// Whether a launcher ignores it or refuses it.
70 severity: Severity,
71 },
72 /// A value of `key` that is no longer read (see [`Rule::set`]).
73 RetiredValue {
74 /// The key whose value it is.
75 key: Key,
76 /// The value as it was written.
77 value: String,
78 /// Why it is no longer read and what to do instead, a clause that
79 /// follows the value.
80 reason: &'static str,
81 /// Whether a launcher ignores it or refuses it.
82 severity: Severity,
83 },
84 /// An environment variable that a setting replaced, as it was written,
85 /// with the key of that setting (see [`retired_env`]).
86 RetiredEnv {
87 /// The variable as it was written.
88 name: String,
89 /// The setting that replaced it.
90 key: Key,
91 },
92 /// `arguments_override` in a rule whose `exe` is `*` (see [`Rule::set`]).
93 ArgumentsOverrideOnAny,
94 /// A list of values for a key that holds one (see [`Rule::set_list`]).
95 NotAList(Key),
96}
97
98/// How a launcher treats a [`FieldError`] from its own settings, as
99/// [`FieldError::severity`] gives it.
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
101pub enum Severity {
102 /// The value is ignored, the rule is left as it was, and the launcher
103 /// prints the error as a warning and goes on.
104 Warning,
105 /// The launcher refuses the settings: what they ask for would not
106 /// happen.
107 Error,
108}
109
110impl FieldError {
111 /// How a launcher treats this error from [`Rule::set`],
112 /// [`Rule::set_list`], [`Rule::set_env`] or [`Key::from_setting`]:
113 /// [`Severity::Warning`] for a retired key or value that asks for
114 /// nothing this Wine could still do, [`Severity::Error`] for everything
115 /// else.
116 ///
117 /// [`Table::parse_lenient`] ignores every field it cannot read, whatever
118 /// its severity.
119 #[must_use]
120 pub const fn severity(&self) -> Severity {
121 match self {
122 Self::RetiredKey { severity, .. } | Self::RetiredValue { severity, .. } => *severity,
123 _ => Severity::Error,
124 }
125 }
126}
127
128impl fmt::Display for FieldError {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Self::UnknownKey(key) => write!(f, "unknown key {key:?}"),
132 Self::BadValue { key, value, .. } => write!(f, "unknown {key} value {value:?}"),
133 Self::Nul => f.write_str("a NUL in its value"),
134 Self::MissingExe => f.write_str("no exe"),
135 Self::EmptyArgument => f.write_str("an empty argument"),
136 Self::EmptyDebugChannel => f.write_str("an empty debug channel"),
137 Self::CommaInDebugChannel(text) => write!(f, "debug channel {text:?} with a ','"),
138 Self::InvalidEnvName(name) => write!(f, "invalid variable name {name:?}"),
139 Self::InvalidDllOverride(text) => {
140 write!(f, "DLL override {text:?} without '=' before its load order")
141 }
142 Self::BadEscape => f.write_str("a '%' without two hex digits after it"),
143 Self::InvalidUtf8 => f.write_str("escapes that are not UTF-8"),
144 Self::MissingEquals => f.write_str("a field without '='"),
145 Self::Duplicate(key) => write!(f, "{key} given more than once"),
146 Self::RetiredKey { key, reason, .. } => write!(f, "{key} {reason}"),
147 Self::RetiredValue {
148 key, value, reason, ..
149 } => write!(f, "{key} value {value:?} {reason}"),
150 Self::RetiredEnv { name, key } => {
151 write!(f, "env {name} is no longer read; use the {key} setting")
152 }
153 Self::ArgumentsOverrideOnAny => f.write_str(
154 "arguments_override needs an exe other than *: for every process it would also \
155 take away the arguments the built-in rules give single programs, which some of \
156 them need to work",
157 ),
158 Self::NotAList(key) => write!(f, "{key} holds one value, not a list"),
159 }
160 }
161}
162
163impl std::error::Error for FieldError {}
164
165/// One problem with the value of [`ENV_VAR`]: the line, the field as it was
166/// written, if the problem is with one, and what is wrong.
167///
168/// Its [`Display`](fmt::Display) form is the warning `compatdb.so` logs, for
169/// example `WINE_COMPATDB line 2: ignoring unknown key "bogus"`, which also
170/// says what [`Table::parse_lenient`] did about it.
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub struct Diagnostic {
173 /// The line, counted from 1, blank lines and a legacy header included.
174 pub line: usize,
175 /// The field as it was written, or `None` for a problem with the whole
176 /// line.
177 pub field: Option<String>,
178 /// What is wrong.
179 pub error: FieldError,
180}
181
182impl fmt::Display for Diagnostic {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 let field = self.field.as_deref().unwrap_or_default();
185 write!(f, "{ENV_VAR} line {}: ", self.line)?;
186 match &self.error {
187 FieldError::MissingExe => f.write_str("ignoring the line, it has no exe"),
188 FieldError::MissingEquals => write!(f, "ignoring field {field:?} without '='"),
189 FieldError::Duplicate(key) => {
190 write!(f, "{key} given more than once, the last one applies")
191 }
192 FieldError::BadEscape => write!(
193 f,
194 "field {field:?} has {}, which is kept as written",
195 self.error
196 ),
197 FieldError::Nul
198 | FieldError::InvalidUtf8
199 | FieldError::EmptyArgument
200 | FieldError::EmptyDebugChannel => {
201 write!(f, "ignoring field {field:?}: {}", self.error)
202 }
203 error => write!(f, "ignoring {error}"),
204 }
205 }
206}
207
208impl std::error::Error for Diagnostic {
209 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
210 Some(&self.error)
211 }
212}
213
214/// Why [`Table::parse_strict`] refused a value of [`ENV_VAR`]: every
215/// problem it found, in line order.
216#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct ParseError(pub(crate) Vec<Diagnostic>);
218
219impl ParseError {
220 /// Every problem, in line order; never empty.
221 #[must_use]
222 pub fn diagnostics(&self) -> &[Diagnostic] {
223 &self.0
224 }
225}
226
227impl fmt::Display for ParseError {
228 /// The diagnostics, one per line.
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 for (i, diagnostic) in self.0.iter().enumerate() {
231 if i > 0 {
232 f.write_str("\n")?;
233 }
234 write!(f, "{diagnostic}")?;
235 }
236 Ok(())
237 }
238}
239
240impl std::error::Error for ParseError {}
241
242#[cfg(test)]
243mod tests;