pmpx_loader/error.rs
1//! What can go wrong: loading a plugin, and one call to it.
2
3use std::fmt;
4use std::path::PathBuf;
5
6/// A plugin could not be loaded, or does not speak this ABI.
7#[derive(Debug)]
8pub enum LoadError {
9 /// The library itself could not be opened: not a dynamic library, wrong architecture, a missing
10 /// dependency of the plugin's own.
11 Open {
12 /// The file that was tried.
13 path: PathBuf,
14 /// The loader's own message.
15 message: String,
16 },
17
18 /// The library opened, but exports no entry symbol.
19 ///
20 /// This is the clean failure for "built against a different contract": the symbol carries the
21 /// root structure's layout, so an old host looking for the old name finds nothing instead of
22 /// reading fields that moved.
23 NoEntrySymbol {
24 /// The file that was tried.
25 path: PathBuf,
26 /// The symbol that was looked for.
27 symbol: &'static str,
28 },
29
30 /// The plugin reports a different semantic major version.
31 Major {
32 /// The file that was tried.
33 path: PathBuf,
34 /// What the plugin says.
35 found: u32,
36 /// What this host speaks.
37 expected: u32,
38 },
39
40 /// The plugin does not provide a capability this host cannot work without.
41 MissingCapability {
42 /// The file that was tried.
43 path: PathBuf,
44 /// The capability's name.
45 capability: &'static str,
46 },
47
48 /// The plugin provides a capability, but its table is smaller than this host knows how to read.
49 ShortTable {
50 /// The file that was tried.
51 path: PathBuf,
52 /// The capability's name.
53 capability: &'static str,
54 /// The size the plugin declared.
55 found: usize,
56 /// The size this host needs.
57 expected: usize,
58 },
59}
60
61impl fmt::Display for LoadError {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 LoadError::Open { path, message } => {
65 write!(f, "cannot load the plugin at {}: {message}", path.display())
66 }
67 LoadError::NoEntrySymbol { path, symbol } => write!(
68 f,
69 "{} exports no {symbol}: it was built against a different version of the plugin \
70 contract, so it has to be rebuilt",
71 path.display()
72 ),
73 LoadError::Major {
74 path,
75 found,
76 expected,
77 } => write!(
78 f,
79 "the plugin at {} speaks ABI major version {found}, this pmpx speaks {expected}; \
80 one of the two has to be updated",
81 path.display()
82 ),
83 LoadError::MissingCapability { path, capability } => write!(
84 f,
85 "the plugin at {} provides no `{capability}` capability, which this pmpx needs",
86 path.display()
87 ),
88 LoadError::ShortTable {
89 path,
90 capability,
91 found,
92 expected,
93 } => write!(
94 f,
95 "the plugin at {} says its `{capability}` table is {found} bytes, but {expected} \
96 are needed to read it safely",
97 path.display()
98 ),
99 }
100 }
101}
102
103impl std::error::Error for LoadError {}
104
105/// What one call to a plugin came back with.
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub enum CallError {
108 /// The plugin does not do that verb.
109 ///
110 /// Not a failure of the run: for `exec` the host may still run the user's command verbatim, and
111 /// that decision belongs to the engine.
112 UnsupportedVerb,
113
114 /// The plugin thinks the call was malformed.
115 InvalidArgs,
116
117 /// The plugin failed internally, including a panic it contained itself.
118 Internal,
119
120 /// A code this host does not know.
121 ///
122 /// Kept as it came: the rule is that an unknown code is treated like [`CallError::Internal`],
123 /// but a host that wants to log the number can.
124 Unknown(u32),
125
126 /// The plugin answered success but filled in a [`pmpx_plugin_abi::PmpxCommand`] smaller than
127 /// this host knows how to read.
128 ShortCommand {
129 /// The size the plugin declared.
130 found: usize,
131 /// The size this host needs.
132 expected: usize,
133 },
134}
135
136impl CallError {
137 /// The code the plugin answered with, for a message that wants to show it.
138 pub fn code(&self) -> Option<u32> {
139 match self {
140 CallError::Unknown(code) => Some(*code),
141 _ => None,
142 }
143 }
144}
145
146impl fmt::Display for CallError {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 CallError::UnsupportedVerb => f.write_str("it does not support that verb"),
150 CallError::InvalidArgs => f.write_str("it thinks the arguments are invalid"),
151 CallError::Internal => {
152 f.write_str("it failed internally or panicked (details on its own stderr)")
153 }
154 CallError::Unknown(code) => write!(f, "it answered with unknown code {code}"),
155 CallError::ShortCommand { found, expected } => write!(
156 f,
157 "it answered with a {found}-byte command structure, but {expected} bytes are \
158 needed to read it safely"
159 ),
160 }
161 }
162}
163
164impl std::error::Error for CallError {}