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);