Skip to main content

provable_contracts/
error.rs

1use 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}