use thiserror::Error;
use crate::jcs::JcsError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormativeReason {
DeadlineOrdering,
RegistrationDigestMismatch,
NoChangeInvariants,
DeadmanInvariants,
OutageNotClean,
CoverageCardinality,
CrossArmCommit,
AckPastUnretained,
FairnessStarvation,
SilentCursorAdvance,
RevisionCross,
AuthnRequired,
LeaseReauth,
RegistrationBound,
AggregateBound,
UnparseableTimestamp,
}
impl NormativeReason {
pub fn as_str(self) -> &'static str {
match self {
Self::DeadlineOrdering => "deadline_ordering",
Self::RegistrationDigestMismatch => "registration_digest_mismatch",
Self::NoChangeInvariants => "no_change_invariants",
Self::DeadmanInvariants => "deadman_invariants",
Self::OutageNotClean => "outage_not_clean",
Self::CoverageCardinality => "coverage_cardinality",
Self::CrossArmCommit => "cross_arm_commit",
Self::AckPastUnretained => "ack_past_unretained",
Self::FairnessStarvation => "fairness_starvation",
Self::SilentCursorAdvance => "silent_cursor_advance",
Self::RevisionCross => "revision_cross",
Self::AuthnRequired => "authn_required",
Self::LeaseReauth => "lease_reauth",
Self::RegistrationBound => "registration_bound",
Self::AggregateBound => "aggregate_bound",
Self::UnparseableTimestamp => "unparseable_timestamp",
}
}
}
impl std::fmt::Display for NormativeReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationError {
pub path: String,
pub constraint: String,
pub reason: Option<NormativeReason>,
}
impl ValidationError {
pub fn new(path: impl Into<String>, constraint: impl Into<String>) -> Self {
Self {
path: path.into(),
constraint: constraint.into(),
reason: None,
}
}
pub fn normative(
path: impl Into<String>,
constraint: impl Into<String>,
reason: NormativeReason,
) -> Self {
Self {
path: path.into(),
constraint: constraint.into(),
reason: Some(reason),
}
}
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.path, self.constraint)
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, Error)]
pub enum Error {
#[error("contract resolution failed at {path}: {constraint}")]
Contract {
path: &'static str,
constraint: &'static str,
},
#[error(transparent)]
Validation(#[from] ValidationError),
#[error(transparent)]
Jcs(#[from] JcsError),
#[error("malformed JSON")]
MalformedJson,
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validation_display_omits_raw_values() {
let err = ValidationError::new("/message_type", "undeclared_message_type");
let shown = err.to_string();
assert!(shown.contains("/message_type"));
assert!(shown.contains("undeclared_message_type"));
assert!(!shown.contains("live_wait_ack"));
assert!(!shown.contains("secret"));
}
}