Skip to main content

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 {}