use crate::error::GlobalError;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ControlPlaneFailureKind {
AdmissionFailure,
ValidationFailure,
ContractResolutionFailure,
CapabilityResolutionFailure,
ProviderResolutionFailure,
NoEligibleDestination,
DestinationUnavailable,
RoutingPolicyFailure,
ControlPlaneUnavailable,
ContextResolutionFailure,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ControlPlaneFailure {
kind: ControlPlaneFailureKind,
error: GlobalError,
}
impl ControlPlaneFailure {
pub fn new(kind: ControlPlaneFailureKind, error: GlobalError) -> Self {
Self { kind, error }
}
pub fn kind(&self) -> ControlPlaneFailureKind {
self.kind
}
pub fn error(&self) -> &GlobalError {
&self.error
}
pub fn into_error(self) -> GlobalError {
self.error
}
}
pub type ControlPlaneResult<T> = Result<T, ControlPlaneFailure>;
#[cfg(test)]
mod tests {
use super::*;
use crate::contracts::Version;
use crate::error::{
DiagnosticDetail, ErrorClass, ErrorCode, ErrorContext, ErrorOwner, ErrorReference, Severity,
};
use crate::identity::{CorrelationId, OperationId};
use crate::operation::{Operation, OperationContext};
use crate::status::Retryability;
use std::collections::HashSet;
fn operation_context() -> OperationContext {
OperationContext::new(Operation::new(
OperationId::new("control-plane-test-operation").unwrap(),
CorrelationId::new("control-plane-test-correlation").unwrap(),
))
}
fn global_error() -> GlobalError {
GlobalError {
code: ErrorCode::new("CORE.CONTROL_PLANE.TEST.001").unwrap(),
owner: ErrorOwner::new("CORE").unwrap(),
version: Version::new(1, 0, 0),
class: ErrorClass::Contract,
severity: Severity::Error,
retryability: Retryability::NonRetryable,
message: "control plane test failure".to_owned(),
details: vec![
DiagnosticDetail::new("stage", "resolution").expect("valid diagnostic detail"),
DiagnosticDetail::new("component", "control-plane")
.expect("valid diagnostic detail"),
],
solution_reference: Some("CORE.CONTROL_PLANE.TEST.HELP".to_owned()),
context: ErrorContext::new(operation_context()),
cause: ErrorReference::new("CORE.TEST.CAUSE.001"),
}
}
#[test]
fn all_control_plane_failure_kinds_are_distinct() {
let kinds = [
ControlPlaneFailureKind::AdmissionFailure,
ControlPlaneFailureKind::ValidationFailure,
ControlPlaneFailureKind::ContractResolutionFailure,
ControlPlaneFailureKind::CapabilityResolutionFailure,
ControlPlaneFailureKind::ProviderResolutionFailure,
ControlPlaneFailureKind::NoEligibleDestination,
ControlPlaneFailureKind::DestinationUnavailable,
ControlPlaneFailureKind::RoutingPolicyFailure,
ControlPlaneFailureKind::ControlPlaneUnavailable,
ControlPlaneFailureKind::ContextResolutionFailure,
];
let unique = kinds.into_iter().collect::<HashSet<_>>();
assert_eq!(kinds.len(), 10);
assert_eq!(unique.len(), 10);
}
#[test]
fn failure_preserves_classification() {
let failure = ControlPlaneFailure::new(
ControlPlaneFailureKind::CapabilityResolutionFailure,
global_error(),
);
assert_eq!(
failure.kind(),
ControlPlaneFailureKind::CapabilityResolutionFailure
);
}
#[test]
fn failure_preserves_global_error() {
let error = global_error();
let failure = ControlPlaneFailure::new(
ControlPlaneFailureKind::ProviderResolutionFailure,
error.clone(),
);
assert_eq!(failure.error(), &error);
}
#[test]
fn into_error_returns_the_original_global_error() {
let error = global_error();
let expected = error.clone();
let failure = ControlPlaneFailure::new(ControlPlaneFailureKind::ValidationFailure, error);
assert_eq!(failure.into_error(), expected);
}
#[test]
fn cause_is_preserved() {
let error = global_error();
let expected_cause = error.cause.clone();
let failure =
ControlPlaneFailure::new(ControlPlaneFailureKind::DestinationUnavailable, error);
assert_eq!(failure.error().cause, expected_cause);
}
#[test]
fn diagnostic_details_are_preserved() {
let error = global_error();
let failure =
ControlPlaneFailure::new(ControlPlaneFailureKind::RoutingPolicyFailure, error);
assert_eq!(
failure.error().details,
vec![
DiagnosticDetail::new("stage", "resolution").unwrap(),
DiagnosticDetail::new("component", "control-plane").unwrap(),
]
);
}
#[test]
fn context_is_preserved() {
let error = global_error();
let failure = ControlPlaneFailure::new(ControlPlaneFailureKind::AdmissionFailure, error);
assert_eq!(
failure.error().context.operation.operation.id.as_str(),
"control-plane-test-operation"
);
assert_eq!(
failure
.error()
.context
.operation
.operation
.correlation_id
.as_str(),
"control-plane-test-correlation"
);
}
#[test]
fn error_metadata_is_preserved() {
let error = global_error();
let failure = ControlPlaneFailure::new(ControlPlaneFailureKind::ValidationFailure, error);
assert_eq!(failure.error().code.as_str(), "CORE.CONTROL_PLANE.TEST.001");
assert_eq!(failure.error().owner.as_str(), "CORE");
assert_eq!(failure.error().version, Version::new(1, 0, 0));
assert_eq!(failure.error().class, ErrorClass::Contract);
assert_eq!(failure.error().severity, Severity::Error);
assert_eq!(failure.error().retryability, Retryability::NonRetryable);
assert_eq!(failure.error().message, "control plane test failure");
assert_eq!(
failure.error().solution_reference.as_deref(),
Some("CORE.CONTROL_PLANE.TEST.HELP")
);
}
#[test]
fn failure_kind_does_not_change_global_error_metadata() {
let error = global_error();
let failure = ControlPlaneFailure::new(
ControlPlaneFailureKind::ControlPlaneUnavailable,
error.clone(),
);
assert_eq!(failure.error(), &error);
}
}