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; query is in an unspecified state")]
15 UnspecifiedQueryState,
16
17 #[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 #[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] 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")]
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
101pub type DynError = Box<dyn StdError + 'static + Send + Sync>;
103
104impl From<Infallible> for Error {
106 fn from(_value: Infallible) -> Self {
107 Error::UnspecifiedQueryState
108 }
109}
110
111pub type Result<T> = StdResult<T, Error>;
113
114#[derive(Debug)]
117pub enum ConfigError {
118 InvalidConfigValue {
120 field_name: String,
121 value: String,
122 reason: String,
123 },
124
125 MissingRequiredConfig {
127 field_name: String,
128 field_type: String,
129 possible_values: Vec<String>,
130 },
131
132 UnrecognizedConfig {
134 field_name: String,
135 field_value: String,
136 possible_confusables: Vec<String>,
137 },
138
139 Unspecified { message: String },
141
142 InternalError { message: String },
144
145 FileNotFound { file_path: String },
147
148 ParseError {
150 source: String,
152 message: String,
153 },
154
155 EnvVarNotSet {
157 env_var_name: String,
159 purpose: String,
161 },
162
163 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}