1use thiserror::Error;
7
8use crate::jcs::JcsError;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum NormativeReason {
13 DeadlineOrdering,
15 RegistrationDigestMismatch,
17 NoChangeInvariants,
19 DeadmanInvariants,
21 OutageNotClean,
23 CoverageCardinality,
25 CrossArmCommit,
27 AckPastUnretained,
29 FairnessStarvation,
31 SilentCursorAdvance,
33 RevisionCross,
35 AuthnRequired,
37 LeaseReauth,
39 RegistrationBound,
41 AggregateBound,
43 UnparseableTimestamp,
45}
46
47impl NormativeReason {
48 pub fn as_str(self) -> &'static str {
50 match self {
51 Self::DeadlineOrdering => "deadline_ordering",
52 Self::RegistrationDigestMismatch => "registration_digest_mismatch",
53 Self::NoChangeInvariants => "no_change_invariants",
54 Self::DeadmanInvariants => "deadman_invariants",
55 Self::OutageNotClean => "outage_not_clean",
56 Self::CoverageCardinality => "coverage_cardinality",
57 Self::CrossArmCommit => "cross_arm_commit",
58 Self::AckPastUnretained => "ack_past_unretained",
59 Self::FairnessStarvation => "fairness_starvation",
60 Self::SilentCursorAdvance => "silent_cursor_advance",
61 Self::RevisionCross => "revision_cross",
62 Self::AuthnRequired => "authn_required",
63 Self::LeaseReauth => "lease_reauth",
64 Self::RegistrationBound => "registration_bound",
65 Self::AggregateBound => "aggregate_bound",
66 Self::UnparseableTimestamp => "unparseable_timestamp",
67 }
68 }
69}
70
71impl std::fmt::Display for NormativeReason {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.write_str(self.as_str())
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct ValidationError {
80 pub path: String,
82 pub constraint: String,
84 pub reason: Option<NormativeReason>,
86}
87
88impl ValidationError {
89 pub fn new(path: impl Into<String>, constraint: impl Into<String>) -> Self {
91 Self {
92 path: path.into(),
93 constraint: constraint.into(),
94 reason: None,
95 }
96 }
97
98 pub fn normative(
100 path: impl Into<String>,
101 constraint: impl Into<String>,
102 reason: NormativeReason,
103 ) -> Self {
104 Self {
105 path: path.into(),
106 constraint: constraint.into(),
107 reason: Some(reason),
108 }
109 }
110}
111
112impl std::fmt::Display for ValidationError {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(f, "{}: {}", self.path, self.constraint)
115 }
116}
117
118impl std::error::Error for ValidationError {}
119
120#[derive(Debug, Error)]
122pub enum Error {
123 #[error("contract resolution failed at {path}: {constraint}")]
125 Contract {
126 path: &'static str,
128 constraint: &'static str,
130 },
131 #[error(transparent)]
133 Validation(#[from] ValidationError),
134 #[error(transparent)]
136 Jcs(#[from] JcsError),
137 #[error("malformed JSON")]
139 MalformedJson,
140}
141
142pub type Result<T> = std::result::Result<T, Error>;
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn validation_display_omits_raw_values() {
151 let err = ValidationError::new("/message_type", "undeclared_message_type");
152 let shown = err.to_string();
153 assert!(shown.contains("/message_type"));
154 assert!(shown.contains("undeclared_message_type"));
155 assert!(!shown.contains("live_wait_ack"));
156 assert!(!shown.contains("secret"));
157 }
158}