use crate::{
metadata::diagnostics::{DiagnosticCategory, Diagnostics},
Error, Result,
};
use std::{fmt, sync::Arc, time::Duration};
#[derive(Debug, Clone)]
pub struct ValidationResult {
outcomes: Vec<ValidationOutcome>,
validator_count: usize,
duration: Duration,
success: bool,
}
impl ValidationResult {
#[must_use]
pub fn success() -> Self {
Self {
outcomes: Vec::new(),
validator_count: 0,
duration: Duration::ZERO,
success: true,
}
}
#[must_use]
pub fn from_results(results: Vec<Result<()>>, duration: Duration) -> Self {
let mut outcomes = Vec::with_capacity(results.len());
let mut success = true;
for (index, result) in results.into_iter().enumerate() {
match result {
Ok(()) => {
outcomes.push(ValidationOutcome::success(format!("Validator {index}")));
}
Err(error) => {
success = false;
outcomes.push(ValidationOutcome::failure(
format!("Validator {index}"),
error,
));
}
}
}
Self {
validator_count: outcomes.len(),
outcomes,
duration,
success,
}
}
#[must_use]
pub fn from_named_results(
named_results: Vec<(&str, Result<()>)>,
duration: Duration,
diagnostics: Option<&Arc<Diagnostics>>,
) -> Self {
let mut outcomes = Vec::with_capacity(named_results.len());
let mut success = true;
let lenient = diagnostics.is_some();
for (name, result) in named_results {
match result {
Ok(()) => {
outcomes.push(ValidationOutcome::success(name.to_string()));
}
Err(error) => {
if lenient {
if let Some(diag) = diagnostics {
diag.warning(
DiagnosticCategory::Validation,
format!("Validation failed in {name}: {error}"),
);
}
outcomes.push(ValidationOutcome::success(name.to_string()));
} else {
success = false;
outcomes.push(ValidationOutcome::failure(name.to_string(), error));
}
}
}
}
Self {
validator_count: outcomes.len(),
outcomes,
duration,
success,
}
}
#[must_use]
pub fn combine(results: Vec<ValidationResult>) -> Self {
let mut combined_outcomes = Vec::new();
let mut total_validator_count = 0;
let mut total_duration = Duration::ZERO;
let mut overall_success = true;
for result in results {
combined_outcomes.extend(result.outcomes);
total_validator_count += result.validator_count;
total_duration += result.duration;
overall_success = overall_success && result.success;
}
Self {
outcomes: combined_outcomes,
validator_count: total_validator_count,
duration: total_duration,
success: overall_success,
}
}
#[must_use]
pub fn is_success(&self) -> bool {
self.success
}
#[must_use]
pub fn is_failure(&self) -> bool {
!self.success
}
#[must_use]
pub fn validator_count(&self) -> usize {
self.validator_count
}
#[must_use]
pub fn duration(&self) -> Duration {
self.duration
}
#[must_use]
pub fn outcomes(&self) -> &[ValidationOutcome] {
&self.outcomes
}
#[must_use]
pub fn failures(&self) -> Vec<&ValidationOutcome> {
self.outcomes
.iter()
.filter(|outcome| outcome.is_failure())
.collect()
}
#[must_use]
pub fn failure_count(&self) -> usize {
self.outcomes
.iter()
.filter(|outcome| outcome.is_failure())
.count()
}
#[must_use]
pub fn errors(&self) -> Vec<&Error> {
self.outcomes
.iter()
.filter_map(|outcome| outcome.error())
.collect()
}
pub fn into_result(self) -> Result<()> {
if self.is_success() {
Ok(())
} else {
let failures = self.failures();
let errors = self.errors().into_iter().cloned().collect::<Vec<_>>();
let error_count = errors.len();
let validator_names: Vec<_> = failures.iter().map(|f| f.validator_name()).collect();
let summary = format!(
"{} of {} validators failed: {}",
error_count,
self.validator_count,
validator_names.join(", ")
);
Err(Error::ValidationStage2Failed {
errors,
error_count,
summary,
})
}
}
#[must_use]
pub fn first_error(&self) -> Option<&Error> {
self.failures().first().and_then(|outcome| outcome.error())
}
}
impl fmt::Display for ValidationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_success() {
write!(
f,
"Validation successful: {} validators passed in {:?}",
self.validator_count, self.duration
)
} else {
write!(
f,
"Validation failed: {} of {} validators failed in {:?}",
self.failure_count(),
self.validator_count,
self.duration
)
}
}
}
#[derive(Debug, Clone)]
pub struct ValidationOutcome {
validator_name: String,
success: bool,
error: Option<Error>,
duration: Duration,
}
impl ValidationOutcome {
#[must_use]
pub fn success(validator_name: String) -> Self {
Self {
validator_name,
success: true,
error: None,
duration: Duration::ZERO,
}
}
#[must_use]
pub fn success_with_duration(validator_name: String, duration: Duration) -> Self {
Self {
validator_name,
success: true,
error: None,
duration,
}
}
#[must_use]
pub fn failure(validator_name: String, error: Error) -> Self {
Self {
validator_name,
success: false,
error: Some(error),
duration: Duration::ZERO,
}
}
#[must_use]
pub fn failure_with_duration(validator_name: String, error: Error, duration: Duration) -> Self {
Self {
validator_name,
success: false,
error: Some(error),
duration,
}
}
#[must_use]
pub fn validator_name(&self) -> &str {
&self.validator_name
}
#[must_use]
pub fn is_success(&self) -> bool {
self.success
}
#[must_use]
pub fn is_failure(&self) -> bool {
!self.success
}
#[must_use]
pub fn error(&self) -> Option<&Error> {
self.error.as_ref()
}
#[must_use]
pub fn duration(&self) -> Duration {
self.duration
}
}
impl fmt::Display for ValidationOutcome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_success() {
write!(f, "{}: SUCCESS ({:?})", self.validator_name, self.duration)
} else {
write!(
f,
"{}: FAILED ({:?}) - {}",
self.validator_name,
self.duration,
self.error
.as_ref()
.map(ToString::to_string)
.as_deref()
.unwrap_or("Unknown error")
)
}
}
}
#[derive(Debug, Clone)]
pub struct TwoStageValidationResult {
stage1_result: Option<ValidationResult>,
stage2_result: Option<ValidationResult>,
total_duration: Duration,
}
impl TwoStageValidationResult {
#[must_use]
pub fn new() -> Self {
Self {
stage1_result: None,
stage2_result: None,
total_duration: Duration::ZERO,
}
}
pub fn set_stage1_result(&mut self, result: ValidationResult) {
self.total_duration += result.duration();
self.stage1_result = Some(result);
}
pub fn set_stage2_result(&mut self, result: ValidationResult) {
self.total_duration += result.duration();
self.stage2_result = Some(result);
}
#[must_use]
pub fn stage1_result(&self) -> Option<&ValidationResult> {
self.stage1_result.as_ref()
}
#[must_use]
pub fn stage2_result(&self) -> Option<&ValidationResult> {
self.stage2_result.as_ref()
}
#[must_use]
pub fn stage1_passed(&self) -> bool {
self.stage1_result
.as_ref()
.is_none_or(ValidationResult::is_success)
}
#[must_use]
pub fn stage2_passed(&self) -> bool {
self.stage2_result
.as_ref()
.is_none_or(ValidationResult::is_success)
}
#[must_use]
pub fn is_success(&self) -> bool {
self.stage1_passed() && self.stage2_passed()
}
#[must_use]
pub fn total_duration(&self) -> Duration {
self.total_duration
}
pub fn into_result(self) -> Result<()> {
if let Some(stage1) = &self.stage1_result {
if stage1.is_failure() {
if let Some(first_error) = stage1.first_error() {
return Err(Error::ValidationStage1Failed {
source: Box::new((*first_error).clone()),
message: format!(
"Stage 1 (raw) validation failed with {} errors",
stage1.failure_count()
),
});
}
}
}
if let Some(stage2) = &self.stage2_result {
if stage2.is_failure() {
return stage2.clone().into_result();
}
}
Ok(())
}
}
impl Default for TwoStageValidationResult {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for TwoStageValidationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Two-stage validation ")?;
if self.is_success() {
write!(f, "successful")?;
} else {
write!(f, "failed")?;
}
write!(f, " (total duration: {:?})", self.total_duration)?;
if let Some(stage1) = &self.stage1_result {
write!(f, "\n Stage 1: {stage1}")?;
}
if let Some(stage2) = &self.stage2_result {
write!(f, "\n Stage 2: {stage2}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
use std::time::Duration;
#[test]
fn test_validation_result_success() {
let result = ValidationResult::success();
assert!(result.is_success());
assert!(!result.is_failure());
assert_eq!(result.validator_count(), 0);
assert_eq!(result.failure_count(), 0);
}
#[test]
fn test_validation_result_from_results() {
let results = vec![Ok(()), Err(Error::NotSupported), Ok(())];
let validation_result = ValidationResult::from_results(results, Duration::from_millis(100));
assert!(!validation_result.is_success());
assert_eq!(validation_result.validator_count(), 3);
assert_eq!(validation_result.failure_count(), 1);
assert_eq!(validation_result.duration(), Duration::from_millis(100));
}
#[test]
fn test_validation_result_from_named_results() {
let results = vec![
("Validator1", Ok(())),
("Validator2", Err(Error::NotSupported)),
];
let validation_result =
ValidationResult::from_named_results(results, Duration::from_millis(50), None);
assert!(!validation_result.is_success());
assert_eq!(validation_result.validator_count(), 2);
assert_eq!(validation_result.failure_count(), 1);
let failures = validation_result.failures();
assert_eq!(failures.len(), 1);
assert_eq!(failures[0].validator_name(), "Validator2");
}
#[test]
fn test_validation_result_combine() {
let result1 = ValidationResult::from_results(vec![Ok(())], Duration::from_millis(10));
let result2 = ValidationResult::from_results(
vec![Err(Error::NotSupported)],
Duration::from_millis(20),
);
let combined = ValidationResult::combine(vec![result1, result2]);
assert!(!combined.is_success());
assert_eq!(combined.validator_count(), 2);
assert_eq!(combined.failure_count(), 1);
assert_eq!(combined.duration(), Duration::from_millis(30));
}
#[test]
fn test_validation_outcome() {
let success_outcome = ValidationOutcome::success("TestValidator".to_string());
assert!(success_outcome.is_success());
assert!(!success_outcome.is_failure());
assert_eq!(success_outcome.validator_name(), "TestValidator");
assert!(success_outcome.error().is_none());
let failure_outcome =
ValidationOutcome::failure("FailValidator".to_string(), Error::NotSupported);
assert!(!failure_outcome.is_success());
assert!(failure_outcome.is_failure());
assert_eq!(failure_outcome.validator_name(), "FailValidator");
assert!(failure_outcome.error().is_some());
}
#[test]
fn test_two_stage_validation_result() {
let mut two_stage = TwoStageValidationResult::new();
let stage1_result = ValidationResult::from_results(vec![Ok(())], Duration::from_millis(10));
let stage2_result = ValidationResult::from_results(
vec![Err(Error::NotSupported)],
Duration::from_millis(20),
);
two_stage.set_stage1_result(stage1_result);
two_stage.set_stage2_result(stage2_result);
assert!(two_stage.stage1_passed());
assert!(!two_stage.stage2_passed());
assert!(!two_stage.is_success());
assert_eq!(two_stage.total_duration(), Duration::from_millis(30));
}
#[test]
fn test_validation_result_into_result() {
let success_result = ValidationResult::success();
assert!(success_result.into_result().is_ok());
let failure_result = ValidationResult::from_results(
vec![Err(Error::NotSupported)],
Duration::from_millis(10),
);
assert!(failure_result.into_result().is_err());
}
}