hipcheck_sdk/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use hipcheck_common::proto::{
4	ConfigurationStatus, InitiateQueryProtocolResponse, SetConfigurationResponse,
5};
6use std::{convert::Infallible, error::Error as StdError, ops::Not, result::Result as StdResult};
7use tokio::sync::mpsc::error::SendError as TokioMpscSendError;
8use tonic::Status as TonicStatus;
9
10/// An enumeration of errors that can occur in a Hipcheck plugin
11#[derive(Debug, thiserror::Error)]
12pub enum Error {
13	/// An unknown error occurred, the query is in an unspecified state
14	#[error("unknown error; query is in an unspecified state")]
15	UnspecifiedQueryState,
16
17	/// The `PluginEngine` received a message with the unexpected status `ReplyInProgress`
18	#[error("unexpected ReplyInProgress state for query")]
19	UnexpectedReplyInProgress,
20
21	#[error("invalid JSON in query key")]
22	InvalidJsonInQueryKey(#[source] serde_json::Error),
23
24	#[error("invalid JSON in query output")]
25	InvalidJsonInQueryOutput(#[source] serde_json::Error),
26
27	#[error("session channel closed unexpectedly")]
28	SessionChannelClosed,
29
30	#[error("failed to send query from session to server")]
31	FailedToSendQueryFromSessionToServer(
32		#[source] TokioMpscSendError<StdResult<InitiateQueryProtocolResponse, TonicStatus>>,
33	),
34
35	/// The `PluginEngine` received a message with a reply-type status when it expected a request
36	#[error("plugin sent QueryReply when server was expecting a request")]
37	ReceivedReplyWhenExpectingRequest,
38
39	/// The `PluginEngine` received a message with a request-type status when it expected a reply
40	#[error("plugin sent QuerySubmit when server was expecting a reply chunk")]
41	ReceivedSubmitWhenExpectingReplyChunk,
42
43	/// The `PluginEngine` received additional messages when it did not expect any
44	#[error("received additional message for ID '{id}' after query completion")]
45	MoreAfterQueryComplete { id: usize },
46
47	#[error("failed to start server")]
48	FailedToStartServer(#[source] tonic::transport::Error),
49
50	/// The `Query::run` function implementation received an incorrectly-typed JSON Value key
51	#[error("unexpected JSON value from plugin")]
52	UnexpectedPluginQueryInputFormat,
53
54	/// The `Query::run` function implementation produced an output that cannot be serialized to JSON
55	#[error("plugin output could not be serialized to JSON")]
56	UnexpectedPluginQueryOutputFormat,
57
58	/// The `PluginEngine` received a request for an unknown query endpoint
59	#[error("could not determine which plugin query to run")]
60	UnknownPluginQuery,
61
62	#[error("invalid format for QueryTarget")]
63	InvalidQueryTargetFormat,
64
65	#[error(transparent)]
66	Unspecified { source: DynError },
67}
68
69impl From<hipcheck_common::error::Error> for Error {
70	fn from(value: hipcheck_common::error::Error) -> Self {
71		use hipcheck_common::error::Error::*;
72		match value {
73			UnspecifiedQueryState => Error::UnspecifiedQueryState,
74			UnexpectedRequestInProgress => Error::UnexpectedReplyInProgress,
75			UnexpectedReplyInProgress => Error::UnexpectedReplyInProgress,
76			ReceivedSubmitWhenExpectingReplyChunk => Error::ReceivedSubmitWhenExpectingReplyChunk,
77			ReceivedReplyWhenExpectingSubmitChunk => Error::ReceivedReplyWhenExpectingRequest,
78			MoreAfterQueryComplete { id } => Error::MoreAfterQueryComplete { id },
79			InvalidJsonInQueryKey(s) => Error::InvalidJsonInQueryKey(s),
80			InvalidJsonInQueryOutput(s) => Error::InvalidJsonInQueryOutput(s),
81		}
82	}
83}
84
85impl From<anyhow::Error> for Error {
86	fn from(value: anyhow::Error) -> Self {
87		Error::Unspecified {
88			source: value.into(),
89		}
90	}
91}
92
93impl Error {
94	pub fn any<E: StdError + 'static + Send + Sync>(source: E) -> Self {
95		Error::Unspecified {
96			source: Box::new(source),
97		}
98	}
99}
100
101/// A thread-safe error trait object.
102pub type DynError = Box<dyn StdError + 'static + Send + Sync>;
103
104// this will never happen, but is needed to enable passing QueryTarget to PluginEngine::query
105impl From<Infallible> for Error {
106	fn from(_value: Infallible) -> Self {
107		Error::UnspecifiedQueryState
108	}
109}
110
111/// A Result type using `hipcheck_sdk::Error`
112pub type Result<T> = StdResult<T, Error>;
113
114/// Errors specific to the execution of `Plugin::set_configuration()` to configure a Hipcheck
115/// plugin.
116#[derive(Debug)]
117pub enum ConfigError {
118	/// The config key was valid, but the associated value was invalid
119	InvalidConfigValue {
120		field_name: String,
121		value: String,
122		reason: String,
123	},
124
125	/// The config was missing an expected field
126	MissingRequiredConfig {
127		field_name: String,
128		field_type: String,
129		possible_values: Vec<String>,
130	},
131
132	/// The config included an unrecognized field
133	UnrecognizedConfig {
134		field_name: String,
135		field_value: String,
136		possible_confusables: Vec<String>,
137	},
138
139	/// An unspecified error
140	Unspecified { message: String },
141
142	/// The plugin encountered an error, probably due to incorrect assumptions.
143	InternalError { message: String },
144
145	/// A necessary plugin input file was not found.
146	FileNotFound { file_path: String },
147
148	/// The plugin's input data could not be parsed correctly.
149	ParseError {
150		// A short name or description of the data source.
151		source: String,
152		message: String,
153	},
154
155	/// An environment variable needed by the plugin was not set.
156	EnvVarNotSet {
157		/// Name of the environment variable
158		env_var_name: String,
159		/// Message describing what the environment variable should contain
160		purpose: String,
161	},
162
163	/// The plugin could not run a needed program.
164	MissingProgram { program_name: String },
165}
166
167impl From<ConfigError> for SetConfigurationResponse {
168	fn from(value: ConfigError) -> Self {
169		match value {
170			ConfigError::InvalidConfigValue {
171				field_name,
172				value,
173				reason,
174			} => SetConfigurationResponse {
175				status: ConfigurationStatus::InvalidConfigurationValue as i32,
176				message: format!("'{value}' for '{field_name}', reason: '{reason}'"),
177			},
178			ConfigError::MissingRequiredConfig {
179				field_name,
180				field_type,
181				possible_values,
182			} => SetConfigurationResponse {
183				status: ConfigurationStatus::MissingRequiredConfiguration as i32,
184				message: {
185					let mut message = format!(
186						"'{field_name}' of type '{field_type}'"
187					);
188
189					if possible_values.is_empty().not() {
190						message.push_str("; possible values: ");
191						message.push_str(&possible_values.join(", "));
192					}
193
194					message
195				},
196			},
197			ConfigError::UnrecognizedConfig {
198				field_name,
199				field_value,
200				possible_confusables,
201			} => SetConfigurationResponse {
202				status: ConfigurationStatus::UnrecognizedConfiguration as i32,
203				message: {
204					let mut message =
205						format!("'{field_name}' with value '{field_value}'");
206
207					if possible_confusables.is_empty().not() {
208						message.push_str("; possible field names: ");
209						message.push_str(&possible_confusables.join(", "));
210					}
211
212					message
213				},
214			},
215			ConfigError::Unspecified { message } => SetConfigurationResponse {
216				status: ConfigurationStatus::Unspecified as i32,
217				message,
218			},
219			ConfigError::InternalError { message } => SetConfigurationResponse {
220				status: ConfigurationStatus::InternalError as i32,
221				message: format!("the plugin encountered an error, probably due to incorrect assumptions: {message}"),
222			},
223			ConfigError::FileNotFound { file_path } => SetConfigurationResponse {
224				status: ConfigurationStatus::FileNotFound as i32,
225				message: file_path,
226			},
227			ConfigError::ParseError {
228				source,
229				message,
230			} => SetConfigurationResponse {
231				status: ConfigurationStatus::ParseError as i32,
232				message: format!("{source} could not be parsed correctly: {message}"),
233			},
234			ConfigError::EnvVarNotSet {
235				env_var_name,
236				purpose,
237			} => SetConfigurationResponse {
238				status: ConfigurationStatus::EnvVarNotSet as i32,
239				message: format!("\"{env_var_name}\". Purpose: {purpose}"),
240			},
241			ConfigError::MissingProgram { program_name } => SetConfigurationResponse {
242				status: ConfigurationStatus::MissingProgram as i32,
243				message: program_name,
244			},
245		}
246	}
247}