cni_plugin/error.rs
1//! When CNI goes bad.
2
3use std::env::VarError;
4
5use regex::Regex;
6use semver::Version;
7use serde_json::Value;
8use thiserror::Error;
9
10use crate::reply::ErrorReply;
11
12/// All errors emitted by this library, plus a few others.
13#[derive(Debug, Error)]
14pub enum CniError {
15 /// Catch-all wrapper for I/O errors.
16 #[error(transparent)]
17 Io(#[from] std::io::Error),
18
19 /// Catch-all wrapper for JSON serialization and deserialization.
20 #[error(transparent)]
21 Json(#[from] serde_json::Error),
22
23 /// When the CNI version requested by the runtime is not supported.
24 ///
25 /// The [`Version`] in the error is the CNI version provided, not ours.
26 ///
27 /// Also see [`VersionReply`][crate::reply::VersionReply].
28 #[error("plugin does not understand CNI version: {0}")]
29 Incompatible(Version),
30
31 /// When nothing is provided on STDIN.
32 #[error("missing input network config")]
33 MissingInput,
34
35 /// When a delegated plugin doesn’t output anything on STDOUT.
36 #[error("missing plugin output")]
37 MissingOutput,
38
39 /// When a required environment variable is missing.
40 #[error("missing environment variable: {var}: {err}")]
41 MissingEnv {
42 /// the variable name
43 var: &'static str,
44
45 /// the underlying error
46 #[source]
47 err: VarError,
48 },
49
50 /// When an environment variable couldn’t be parsed or is invalid.
51 #[error("environment variable has invalid format: {var}: {err}")]
52 InvalidEnv {
53 /// the variable name
54 var: &'static str,
55
56 /// the underlying error
57 #[source]
58 err: Box<dyn std::error::Error>,
59 },
60
61 /// When the current working directory cannot be obtained (for delegation).
62 #[error("cannot obtain current working directory")]
63 NoCwd,
64
65 /// When a delegated plugin cannot be found on `CNI_PATH`.
66 #[error("missing (or not on CNI_PATH) plugin {name}: {err}")]
67 MissingPlugin {
68 /// the name of the plugin binary
69 name: String,
70
71 /// the underlying error
72 #[source]
73 err: which::Error,
74 },
75
76 /// Wrapper for errors in relation to a delegated plugin.
77 #[error("with plugin {plugin}: {err}")]
78 Delegated {
79 /// the name of the plugin binary
80 plugin: String,
81
82 /// the underlying error
83 err: Box<Self>,
84 },
85
86 /// A generic error as a string.
87 ///
88 /// This error variant is not used in the library, but is provided for
89 /// plugin implementations to make use of without needing to make their own
90 /// error type.
91 ///
92 /// # Example
93 ///
94 /// ```
95 /// # use cni_plugin::error::CniError;
96 /// CniError::Generic("a total catastrophe".into());
97 /// ```
98 #[error("{0}")]
99 Generic(String),
100
101 /// A debug error as anything that implements [`Debug`][std::fmt::Debug].
102 ///
103 /// This error variant is not used in the library, but is provided for
104 /// plugin implementations to make use of without needing to make their own
105 /// error type.
106 ///
107 /// # Example
108 ///
109 /// ```
110 /// # use cni_plugin::error::CniError;
111 /// CniError::Debug(Box::new(("hello", "world", vec![1, 2, 3])));
112 /// ```
113 #[error("{0:?}")]
114 Debug(Box<dyn std::fmt::Debug>),
115
116 /// When a field in configuration is missing.
117 ///
118 /// This error variant is not used in the library, but is provided for
119 /// plugin implementations to make use of without needing to make their own
120 /// error type.
121 ///
122 /// # Example
123 ///
124 /// ```
125 /// # use cni_plugin::error::CniError;
126 /// CniError::MissingField("ipam.type");
127 /// ```
128 #[error("can't proceed without {0} field")]
129 MissingField(&'static str),
130
131 /// When a field in configuration is invalid.
132 ///
133 /// This error variant is not used in the library, but is provided for
134 /// plugin implementations to make use of without needing to make their own
135 /// error type.
136 ///
137 /// # Example
138 ///
139 /// ```
140 /// # use cni_plugin::error::CniError;
141 /// # use serde_json::Value;
142 /// CniError::InvalidField {
143 /// field: "ipam.pool",
144 /// expected: "string",
145 /// value: Value::Null,
146 /// };
147 /// ```
148 #[error("{field}: expected {expected}, got: {value:?}")]
149 InvalidField {
150 /// the name or path of the invalid field
151 field: &'static str,
152
153 /// the value or type the field was expected to be
154 expected: &'static str,
155
156 /// the actual value or a facsimile thereof
157 value: Value,
158 },
159}
160
161impl CniError {
162 /// Convert a CniError into an ErrorReply.
163 ///
164 /// [`ErrorReply`]s can be used with [`reply`][crate::reply::reply], but
165 /// require `cni_version` to be set to the input configuration’s. This
166 /// method makes it easier to create errors (including with the `?`
167 /// operator, from foreign error types) and only populate the version field
168 /// when ready to send the reply.
169 ///
170 /// It’s recommended to add an implementation of this if you make your own
171 /// error type.
172 pub fn into_reply(self, cni_version: Version) -> ErrorReply<'static> {
173 match self {
174 Self::Io(e) => ErrorReply {
175 cni_version,
176 code: 5,
177 msg: "I/O error",
178 details: e.to_string(),
179 },
180 Self::Json(e) => ErrorReply {
181 cni_version,
182 code: 6,
183 msg: "Cannot decode JSON payload",
184 details: e.to_string(),
185 },
186 e @ Self::Incompatible(_) => ErrorReply {
187 cni_version,
188 code: 1,
189 msg: "Incompatible CNI version",
190 details: e.to_string(),
191 },
192 e @ Self::MissingInput => ErrorReply {
193 cni_version,
194 code: 7,
195 msg: "Missing payload",
196 details: e.to_string(),
197 },
198 e @ Self::MissingOutput => ErrorReply {
199 cni_version,
200 code: 7,
201 msg: "Missing output",
202 details: e.to_string(),
203 },
204 e @ Self::MissingEnv { .. } => ErrorReply {
205 cni_version,
206 code: 4,
207 msg: "Missing environment variable",
208 details: e.to_string(),
209 },
210 e @ Self::InvalidEnv { .. } => ErrorReply {
211 cni_version,
212 code: 4,
213 msg: "Invalid environment variable",
214 details: e.to_string(),
215 },
216 e @ Self::NoCwd => ErrorReply {
217 cni_version,
218 code: 5,
219 msg: "Bad workdir",
220 details: e.to_string(),
221 },
222 e @ Self::MissingPlugin { .. } => ErrorReply {
223 cni_version,
224 code: 5,
225 msg: "Missing plugin",
226 details: e.to_string(),
227 },
228 e @ Self::Delegated { .. } => ErrorReply {
229 cni_version,
230 code: 5,
231 msg: "Delegated",
232 details: e.to_string(),
233 },
234 Self::Generic(s) => ErrorReply {
235 cni_version,
236 code: 100,
237 msg: "ERROR",
238 details: s,
239 },
240 e @ Self::Debug { .. } => ErrorReply {
241 cni_version,
242 code: 101,
243 msg: "DEBUG",
244 details: e.to_string(),
245 },
246 e @ Self::MissingField(_) => ErrorReply {
247 cni_version,
248 code: 104,
249 msg: "Missing field",
250 details: e.to_string(),
251 },
252 e @ Self::InvalidField { .. } => ErrorReply {
253 cni_version,
254 code: 107,
255 msg: "Invalid field",
256 details: e.to_string(),
257 },
258 }
259 }
260}
261
262/// Underlying error used for an empty value that shouldn’t be.
263///
264/// Used with [`CniError::InvalidEnv`].
265#[derive(Clone, Copy, Debug, Error)]
266#[error("must not be empty")]
267pub struct EmptyValueError;
268
269/// Underlying error used for an invalid `CNI_COMMAND`.
270///
271/// Used with [`CniError::InvalidEnv`].
272#[derive(Clone, Copy, Debug, Error)]
273#[error("must be one of ADD, DEL, CHECK, VERSION")]
274pub struct InvalidCommandError;
275
276/// Underlying error used for a value that should match a regex but doesn’t.
277///
278/// Used with [`CniError::InvalidEnv`].
279#[derive(Clone, Debug, Error)]
280#[error("must match regex: {0}")]
281pub struct RegexValueError(pub Regex);