use hipcheck_common::proto::{
ConfigurationStatus, InitiateQueryProtocolResponse, SetConfigurationResponse,
};
use std::{convert::Infallible, error::Error as StdError, ops::Not, result::Result as StdResult};
use tokio::sync::mpsc::error::SendError as TokioMpscSendError;
use tonic::Status as TonicStatus;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("unknown error")]
UnspecifiedQueryState,
#[error("unexpected ReplyInProgress state for query")]
UnexpectedReplyInProgress,
#[error("invalid JSON in query key")]
InvalidJsonInQueryKey(#[source] Box<serde_json::Error>),
#[error("invalid JSON in query output")]
InvalidJsonInQueryOutput(#[source] Box<serde_json::Error>),
#[error("session channel closed unexpectedly")]
SessionChannelClosed,
#[error("failed to send query from session to server")]
FailedToSendQueryFromSessionToServer(
#[source] Box<TokioMpscSendError<StdResult<InitiateQueryProtocolResponse, TonicStatus>>>,
),
#[error("plugin sent QueryReply when server was expecting a request")]
ReceivedReplyWhenExpectingRequest,
#[error("plugin sent QuerySubmit when server was expecting a reply chunk")]
ReceivedSubmitWhenExpectingReplyChunk,
#[error("received additional message for ID '{id}' after query completion")]
MoreAfterQueryComplete { id: usize },
#[error("failed to start server")]
FailedToStartServer(#[source] Box<tonic::transport::Error>),
#[error("unexpected JSON value from plugin")]
UnexpectedPluginQueryInputFormat,
#[error("plugin output could not be serialized to JSON")]
UnexpectedPluginQueryOutputFormat,
#[error("could not determine which plugin query to run for '{0}'")]
UnknownPluginQuery(Box<str>),
#[error("invalid format for QueryTarget")]
InvalidQueryTargetFormat,
#[error("the plugin was called with a default query, but none is defined")]
NoDefaultQuery,
#[error("the query is unsupported for the target")]
QueryUnsupportedForTarget,
#[error(transparent)]
Unspecified { source: DynError },
}
impl From<hipcheck_common::error::Error> for Error {
fn from(value: hipcheck_common::error::Error) -> Self {
use hipcheck_common::error::Error::*;
match value {
UnspecifiedQueryState => Error::UnspecifiedQueryState,
UnexpectedRequestInProgress => Error::UnexpectedReplyInProgress,
UnexpectedReplyInProgress => Error::UnexpectedReplyInProgress,
ReceivedSubmitWhenExpectingReplyChunk => Error::ReceivedSubmitWhenExpectingReplyChunk,
ReceivedReplyWhenExpectingSubmitChunk => Error::ReceivedReplyWhenExpectingRequest,
MoreAfterQueryComplete { id } => Error::MoreAfterQueryComplete { id },
InvalidJsonInQueryKey(s) => Error::InvalidJsonInQueryKey(Box::new(s)),
InvalidJsonInQueryOutput(s) => Error::InvalidJsonInQueryOutput(Box::new(s)),
QueryUnsupportedForTarget => Error::QueryUnsupportedForTarget,
}
}
}
impl From<anyhow::Error> for Error {
fn from(value: anyhow::Error) -> Self {
Error::Unspecified {
source: value.into(),
}
}
}
impl Error {
pub fn any<E: StdError + 'static + Send + Sync>(source: E) -> Self {
Error::Unspecified {
source: Box::new(source),
}
}
}
pub type DynError = Box<dyn StdError + 'static + Send + Sync>;
impl From<Infallible> for Error {
fn from(_value: Infallible) -> Self {
Error::UnspecifiedQueryState
}
}
pub type Result<T> = StdResult<T, Error>;
pub type ConfigResult<T> = StdResult<T, ConfigError>;
#[derive(Debug)]
pub enum ConfigError {
InvalidConfigValue {
field_name: Box<str>,
value: Box<str>,
reason: Box<str>,
},
MissingRequiredConfig {
field_name: Box<str>,
field_type: Box<str>,
possible_values: Vec<Box<str>>,
},
UnrecognizedConfig {
field_name: Box<str>,
field_value: Box<str>,
possible_confusables: Vec<Box<str>>,
},
Unspecified { message: Box<str> },
InternalError { message: Box<str> },
FileNotFound { file_path: Box<str> },
ParseError {
source: Box<str>,
message: Box<str>,
},
EnvVarNotSet {
env_var_name: Box<str>,
purpose: Box<str>,
},
MissingProgram { program_name: Box<str> },
}
impl From<ConfigError> for SetConfigurationResponse {
fn from(value: ConfigError) -> Self {
match value {
ConfigError::InvalidConfigValue {
field_name,
value,
reason,
} => SetConfigurationResponse {
status: ConfigurationStatus::InvalidConfigurationValue as i32,
message: format!("'{value}' for '{field_name}', reason: '{reason}'"),
},
ConfigError::MissingRequiredConfig {
field_name,
field_type,
possible_values,
} => SetConfigurationResponse {
status: ConfigurationStatus::MissingRequiredConfiguration as i32,
message: {
let mut message = format!("'{field_name}' of type '{field_type}'");
if possible_values.is_empty().not() {
message.push_str("; possible values: ");
message.push_str(&possible_values.join(", "));
}
message
},
},
ConfigError::UnrecognizedConfig {
field_name,
field_value,
possible_confusables,
} => SetConfigurationResponse {
status: ConfigurationStatus::UnrecognizedConfiguration as i32,
message: {
let mut message = format!("'{field_name}' with value '{field_value}'");
if possible_confusables.is_empty().not() {
message.push_str("; possible field names: ");
message.push_str(&possible_confusables.join(", "));
}
message
},
},
ConfigError::Unspecified { message } => SetConfigurationResponse {
status: ConfigurationStatus::Unspecified as i32,
message: message.to_string(),
},
ConfigError::InternalError { message } => SetConfigurationResponse {
status: ConfigurationStatus::InternalError as i32,
message: format!(
"the plugin encountered an error, probably due to incorrect assumptions: {message}"
),
},
ConfigError::FileNotFound { file_path } => SetConfigurationResponse {
status: ConfigurationStatus::FileNotFound as i32,
message: file_path.to_string(),
},
ConfigError::ParseError { source, message } => SetConfigurationResponse {
status: ConfigurationStatus::ParseError as i32,
message: format!("{source} could not be parsed correctly: {message}"),
},
ConfigError::EnvVarNotSet {
env_var_name,
purpose,
} => SetConfigurationResponse {
status: ConfigurationStatus::EnvVarNotSet as i32,
message: format!("\"{env_var_name}\". Purpose: {purpose}"),
},
ConfigError::MissingProgram { program_name } => SetConfigurationResponse {
status: ConfigurationStatus::MissingProgram as i32,
message: program_name.to_string(),
},
}
}
}