1use 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#[derive(Debug, thiserror::Error)]
12pub enum Error {
13 #[error("unknown error")]
15 UnspecifiedQueryState,
16
17 #[error("unexpected ReplyInProgress state for query")]
19 UnexpectedReplyInProgress,
20
21 #[error("invalid JSON in query key")]
22 InvalidJsonInQueryKey(#[source] Box<serde_json::Error>),
23
24 #[error("invalid JSON in query output")]
25 InvalidJsonInQueryOutput(#[source] Box<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] Box<TokioMpscSendError<StdResult<InitiateQueryProtocolResponse, TonicStatus>>>,
33 ),
34
35 #[error("plugin sent QueryReply when server was expecting a request")]
37 ReceivedReplyWhenExpectingRequest,
38
39 #[error("plugin sent QuerySubmit when server was expecting a reply chunk")]
41 ReceivedSubmitWhenExpectingReplyChunk,
42
43 #[error("received additional message for ID '{id}' after query completion")]
45 MoreAfterQueryComplete { id: usize },
46
47 #[error("failed to start server")]
48 FailedToStartServer(#[source] Box<tonic::transport::Error>),
49
50 #[error("unexpected JSON value from plugin")]
52 UnexpectedPluginQueryInputFormat,
53
54 #[error("plugin output could not be serialized to JSON")]
56 UnexpectedPluginQueryOutputFormat,
57
58 #[error("could not determine which plugin query to run for '{0}'")]
60 UnknownPluginQuery(Box<str>),
61
62 #[error("invalid format for QueryTarget")]
63 InvalidQueryTargetFormat,
64
65 #[error("the plugin was called with a default query, but none is defined")]
66 NoDefaultQuery,
67
68 #[error("the query is unsupported for the target")]
69 QueryUnsupportedForTarget,
70
71 #[error(transparent)]
72 Unspecified { source: DynError },
73}
74
75impl From<hipcheck_common::error::Error> for Error {
76 fn from(value: hipcheck_common::error::Error) -> Self {
77 use hipcheck_common::error::Error::*;
78 match value {
79 UnspecifiedQueryState => Error::UnspecifiedQueryState,
80 UnexpectedRequestInProgress => Error::UnexpectedReplyInProgress,
81 UnexpectedReplyInProgress => Error::UnexpectedReplyInProgress,
82 ReceivedSubmitWhenExpectingReplyChunk => Error::ReceivedSubmitWhenExpectingReplyChunk,
83 ReceivedReplyWhenExpectingSubmitChunk => Error::ReceivedReplyWhenExpectingRequest,
84 MoreAfterQueryComplete { id } => Error::MoreAfterQueryComplete { id },
85 InvalidJsonInQueryKey(s) => Error::InvalidJsonInQueryKey(Box::new(s)),
86 InvalidJsonInQueryOutput(s) => Error::InvalidJsonInQueryOutput(Box::new(s)),
87 QueryUnsupportedForTarget => Error::QueryUnsupportedForTarget,
88 }
89 }
90}
91
92impl From<anyhow::Error> for Error {
93 fn from(value: anyhow::Error) -> Self {
94 Error::Unspecified {
95 source: value.into(),
96 }
97 }
98}
99
100impl Error {
101 pub fn any<E: StdError + 'static + Send + Sync>(source: E) -> Self {
102 Error::Unspecified {
103 source: Box::new(source),
104 }
105 }
106}
107
108pub type DynError = Box<dyn StdError + 'static + Send + Sync>;
110
111impl From<Infallible> for Error {
113 fn from(_value: Infallible) -> Self {
114 Error::UnspecifiedQueryState
115 }
116}
117
118pub type Result<T> = StdResult<T, Error>;
120
121pub type ConfigResult<T> = StdResult<T, ConfigError>;
123
124#[derive(Debug)]
127pub enum ConfigError {
128 InvalidConfigValue {
130 field_name: Box<str>,
131 value: Box<str>,
132 reason: Box<str>,
133 },
134
135 MissingRequiredConfig {
137 field_name: Box<str>,
138 field_type: Box<str>,
139 possible_values: Vec<Box<str>>,
140 },
141
142 UnrecognizedConfig {
144 field_name: Box<str>,
145 field_value: Box<str>,
146 possible_confusables: Vec<Box<str>>,
147 },
148
149 Unspecified { message: Box<str> },
151
152 InternalError { message: Box<str> },
154
155 FileNotFound { file_path: Box<str> },
157
158 ParseError {
160 source: Box<str>,
162 message: Box<str>,
163 },
164
165 EnvVarNotSet {
167 env_var_name: Box<str>,
169 purpose: Box<str>,
171 },
172
173 MissingProgram { program_name: Box<str> },
175}
176
177impl From<ConfigError> for SetConfigurationResponse {
178 fn from(value: ConfigError) -> Self {
179 match value {
180 ConfigError::InvalidConfigValue {
181 field_name,
182 value,
183 reason,
184 } => SetConfigurationResponse {
185 status: ConfigurationStatus::InvalidConfigurationValue as i32,
186 message: format!("'{value}' for '{field_name}', reason: '{reason}'"),
187 },
188 ConfigError::MissingRequiredConfig {
189 field_name,
190 field_type,
191 possible_values,
192 } => SetConfigurationResponse {
193 status: ConfigurationStatus::MissingRequiredConfiguration as i32,
194 message: {
195 let mut message = format!("'{field_name}' of type '{field_type}'");
196
197 if possible_values.is_empty().not() {
198 message.push_str("; possible values: ");
199 message.push_str(&possible_values.join(", "));
200 }
201
202 message
203 },
204 },
205 ConfigError::UnrecognizedConfig {
206 field_name,
207 field_value,
208 possible_confusables,
209 } => SetConfigurationResponse {
210 status: ConfigurationStatus::UnrecognizedConfiguration as i32,
211 message: {
212 let mut message = format!("'{field_name}' with value '{field_value}'");
213
214 if possible_confusables.is_empty().not() {
215 message.push_str("; possible field names: ");
216 message.push_str(&possible_confusables.join(", "));
217 }
218
219 message
220 },
221 },
222 ConfigError::Unspecified { message } => SetConfigurationResponse {
223 status: ConfigurationStatus::Unspecified as i32,
224 message: message.to_string(),
225 },
226 ConfigError::InternalError { message } => SetConfigurationResponse {
227 status: ConfigurationStatus::InternalError as i32,
228 message: format!(
229 "the plugin encountered an error, probably due to incorrect assumptions: {message}"
230 ),
231 },
232 ConfigError::FileNotFound { file_path } => SetConfigurationResponse {
233 status: ConfigurationStatus::FileNotFound as i32,
234 message: file_path.to_string(),
235 },
236 ConfigError::ParseError { source, message } => SetConfigurationResponse {
237 status: ConfigurationStatus::ParseError as i32,
238 message: format!("{source} could not be parsed correctly: {message}"),
239 },
240 ConfigError::EnvVarNotSet {
241 env_var_name,
242 purpose,
243 } => SetConfigurationResponse {
244 status: ConfigurationStatus::EnvVarNotSet as i32,
245 message: format!("\"{env_var_name}\". Purpose: {purpose}"),
246 },
247 ConfigError::MissingProgram { program_name } => SetConfigurationResponse {
248 status: ConfigurationStatus::MissingProgram as i32,
249 message: program_name.to_string(),
250 },
251 }
252 }
253}