Skip to main content

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;