#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
ComponentNotFound,
Internal,
InvalidComponent,
InvalidConnection,
InvalidGraph,
ValidationErrors(Vec<ValidationError>),
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Self::ComponentNotFound => "ComponentNotFound",
Self::Internal => "Internal",
Self::InvalidComponent => "InvalidComponent",
Self::InvalidConnection => "InvalidConnection",
Self::InvalidGraph => "InvalidGraph",
Self::ValidationErrors(_) => "ValidationErrors",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Error {
kind: ErrorKind,
desc: String,
}
impl Error {
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub(crate) fn into_validation_errors(self) -> Result<Vec<ValidationError>, Error> {
match self.kind {
ErrorKind::ValidationErrors(errors) => Ok(errors),
kind => Err(Error {
kind,
desc: self.desc,
}),
}
}
}
impl Error {
pub(crate) fn component_not_found(desc: impl Into<String>) -> Self {
Self {
kind: ErrorKind::ComponentNotFound,
desc: desc.into(),
}
}
pub(crate) fn internal(desc: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Internal,
desc: desc.into(),
}
}
pub(crate) fn invalid_component(desc: impl Into<String>) -> Self {
Self {
kind: ErrorKind::InvalidComponent,
desc: desc.into(),
}
}
pub(crate) fn invalid_connection(desc: impl Into<String>) -> Self {
Self {
kind: ErrorKind::InvalidConnection,
desc: desc.into(),
}
}
pub(crate) fn invalid_graph(desc: impl Into<String>) -> Self {
Self {
kind: ErrorKind::InvalidGraph,
desc: desc.into(),
}
}
pub(crate) fn validation_errors(errors: Vec<ValidationError>) -> Self {
Self {
kind: ErrorKind::ValidationErrors(errors),
desc: String::new(),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
ErrorKind::ValidationErrors(errors) => {
write!(f, "Graph validation failed:")?;
for error in errors {
write!(f, "\n {error}")?;
}
Ok(())
}
kind => write!(f, "{kind}: {}", self.desc),
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, PartialEq)]
pub struct ValidationError {
message: String,
component_ids: Vec<u64>,
}
impl ValidationError {
pub(crate) fn new(message: impl Into<String>, component_ids: impl Into<Vec<u64>>) -> Self {
Self {
message: message.into(),
component_ids: component_ids.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
pub fn component_ids(&self) -> &[u64] {
&self.component_ids
}
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for ValidationError {}
impl From<ValidationError> for Error {
fn from(error: ValidationError) -> Self {
Error::validation_errors(vec![error])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validation_error_exposes_its_message_and_components() {
let error = ValidationError::new("boom", [3u64, 4]);
assert_eq!(error.message(), "boom");
assert_eq!(error.component_ids(), &[3, 4]);
assert_eq!(error.to_string(), "boom");
}
#[test]
fn leaf_error_display_is_unchanged() {
assert_eq!(
Error::invalid_graph("No grid component found.").to_string(),
"InvalidGraph: No grid component found."
);
}
#[test]
fn validation_errors_display_lists_each_failure() {
let error = Error::validation_errors(vec![
ValidationError::new("first problem", [1u64]),
ValidationError::new("second problem", [2u64, 3]),
]);
assert_eq!(
error.to_string(),
"Graph validation failed:\n first problem\n second problem"
);
}
#[test]
fn into_validation_errors_unwraps_the_collected_failures() {
let error: Error = ValidationError::new("boom", [1u64]).into();
assert_eq!(
error.into_validation_errors(),
Ok(vec![ValidationError::new("boom", [1u64])])
);
}
#[test]
fn into_validation_errors_passes_other_kinds_through() {
assert_eq!(
Error::internal("bug").into_validation_errors(),
Err(Error::internal("bug"))
);
}
}