provable_contracts/
error.rs1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum ContractError {
5 #[error("Failed to read contract file: {0}")]
6 Io(#[from] std::io::Error),
7
8 #[error("Failed to parse YAML: {0}")]
9 Yaml(#[from] serde_yaml::Error),
10
11 #[error("Schema violation: {0}")]
12 Schema(String),
13
14 #[error("Missing required field: {section}.{field}")]
15 MissingField { section: String, field: String },
16
17 #[error("Invalid reference: {from} references non-existent {to}")]
18 InvalidReference { from: String, to: String },
19
20 #[error("Duplicate ID: {id} in {section}")]
21 DuplicateId { id: String, section: String },
22}
23
24#[derive(Debug, Clone)]
25pub struct Violation {
26 pub severity: Severity,
27 pub rule: String,
28 pub message: String,
29 pub location: Option<String>,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum Severity {
34 Error,
35 Warning,
36 Info,
37}
38
39impl std::fmt::Display for Violation {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 let prefix = match self.severity {
42 Severity::Error => "ERROR",
43 Severity::Warning => "WARN",
44 Severity::Info => "INFO",
45 };
46 write!(f, "[{prefix}] {}: {}", self.rule, self.message)
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn violation_display_error() {
56 let v = Violation {
57 severity: Severity::Error,
58 rule: "SCHEMA-001".to_string(),
59 message: "test error".to_string(),
60 location: Some("metadata".to_string()),
61 };
62 let s = v.to_string();
63 assert!(s.contains("[ERROR]"));
64 assert!(s.contains("SCHEMA-001"));
65 assert!(s.contains("test error"));
66 }
67
68 #[test]
69 fn violation_display_warning() {
70 let v = Violation {
71 severity: Severity::Warning,
72 rule: "SCHEMA-006".to_string(),
73 message: "test warning".to_string(),
74 location: None,
75 };
76 let s = v.to_string();
77 assert!(s.contains("[WARN]"));
78 }
79
80 #[test]
81 fn violation_display_info() {
82 let v = Violation {
83 severity: Severity::Info,
84 rule: "INFO-001".to_string(),
85 message: "informational".to_string(),
86 location: None,
87 };
88 let s = v.to_string();
89 assert!(s.contains("[INFO]"));
90 }
91
92 #[test]
93 fn contract_error_io() {
94 let err = ContractError::Io(std::io::Error::new(
95 std::io::ErrorKind::NotFound,
96 "not found",
97 ));
98 let s = err.to_string();
99 assert!(s.contains("Failed to read"));
100 }
101
102 #[test]
103 fn contract_error_schema() {
104 let err = ContractError::Schema("bad schema".to_string());
105 assert!(err.to_string().contains("Schema violation"));
106 }
107
108 #[test]
109 fn contract_error_missing_field() {
110 let err = ContractError::MissingField {
111 section: "metadata".to_string(),
112 field: "version".to_string(),
113 };
114 let s = err.to_string();
115 assert!(s.contains("metadata"));
116 assert!(s.contains("version"));
117 }
118
119 #[test]
120 fn contract_error_invalid_reference() {
121 let err = ContractError::InvalidReference {
122 from: "harness".to_string(),
123 to: "obligation".to_string(),
124 };
125 let s = err.to_string();
126 assert!(s.contains("harness"));
127 assert!(s.contains("obligation"));
128 }
129
130 #[test]
131 fn contract_error_duplicate_id() {
132 let err = ContractError::DuplicateId {
133 id: "KANI-001".to_string(),
134 section: "kani_harnesses".to_string(),
135 };
136 let s = err.to_string();
137 assert!(s.contains("KANI-001"));
138 }
139}