windjammer_runtime/
contracts.rs1use std::fmt;
6
7#[derive(Debug, Clone)]
9pub struct ContractViolation {
10 pub kind: ContractKind,
11 pub condition: String,
12 pub message: Option<String>,
13}
14
15impl ContractViolation {
16 pub fn new(kind: ContractKind, condition: String) -> Self {
17 Self {
18 kind,
19 condition,
20 message: None,
21 }
22 }
23
24 pub fn with_message(mut self, message: String) -> Self {
25 self.message = Some(message);
26 self
27 }
28}
29
30impl fmt::Display for ContractViolation {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "{} violation: {}", self.kind, self.condition)?;
33 if let Some(msg) = &self.message {
34 write!(f, " ({})", msg)?;
35 }
36 Ok(())
37 }
38}
39
40impl std::error::Error for ContractViolation {}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum ContractKind {
45 Precondition,
46 Postcondition,
47 Invariant,
48}
49
50impl fmt::Display for ContractKind {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 ContractKind::Precondition => write!(f, "Precondition"),
54 ContractKind::Postcondition => write!(f, "Postcondition"),
55 ContractKind::Invariant => write!(f, "Invariant"),
56 }
57 }
58}
59
60pub fn requires(condition: bool, description: &str) {
72 if !condition {
73 panic!(
74 "{}",
75 ContractViolation::new(ContractKind::Precondition, description.to_string())
76 );
77 }
78}
79
80pub fn ensures(condition: bool, description: &str) {
93 if !condition {
94 panic!(
95 "{}",
96 ContractViolation::new(ContractKind::Postcondition, description.to_string())
97 );
98 }
99}
100
101pub fn invariant(condition: bool, description: &str) {
119 if !condition {
120 panic!(
121 "{}",
122 ContractViolation::new(ContractKind::Invariant, description.to_string())
123 );
124 }
125}
126
127#[inline(always)]
140pub fn old<T: Clone>(value: T) -> T {
141 value.clone()
142}
143
144pub struct Contract {
146 preconditions: Vec<(bool, String)>,
147 postconditions: Vec<(bool, String)>,
148 invariants: Vec<(bool, String)>,
149}
150
151impl Contract {
152 pub fn new() -> Self {
153 Self {
154 preconditions: Vec::new(),
155 postconditions: Vec::new(),
156 invariants: Vec::new(),
157 }
158 }
159
160 pub fn requires(mut self, condition: bool, description: &str) -> Self {
161 self.preconditions
162 .push((condition, description.to_string()));
163 self
164 }
165
166 pub fn ensures(mut self, condition: bool, description: &str) -> Self {
167 self.postconditions
168 .push((condition, description.to_string()));
169 self
170 }
171
172 pub fn invariant(mut self, condition: bool, description: &str) -> Self {
173 self.invariants.push((condition, description.to_string()));
174 self
175 }
176
177 pub fn check_preconditions(&self) {
178 for (condition, desc) in &self.preconditions {
179 if !condition {
180 panic!(
181 "{}",
182 ContractViolation::new(ContractKind::Precondition, desc.clone())
183 );
184 }
185 }
186 }
187
188 pub fn check_postconditions(&self) {
189 for (condition, desc) in &self.postconditions {
190 if !condition {
191 panic!(
192 "{}",
193 ContractViolation::new(ContractKind::Postcondition, desc.clone())
194 );
195 }
196 }
197 }
198
199 pub fn check_invariants(&self) {
200 for (condition, desc) in &self.invariants {
201 if !condition {
202 panic!(
203 "{}",
204 ContractViolation::new(ContractKind::Invariant, desc.clone())
205 );
206 }
207 }
208 }
209}
210
211impl Default for Contract {
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_requires_success() {
223 requires(true, "should not panic");
224 }
226
227 #[test]
228 #[should_panic(expected = "Precondition violation")]
229 fn test_requires_failure() {
230 requires(false, "should panic");
231 }
232
233 #[test]
234 fn test_ensures_success() {
235 ensures(true, "should not panic");
236 }
238
239 #[test]
240 #[should_panic(expected = "Postcondition violation")]
241 fn test_ensures_failure() {
242 ensures(false, "should panic");
243 }
244
245 #[test]
246 fn test_invariant_success() {
247 invariant(true, "should not panic");
248 }
250
251 #[test]
252 #[should_panic(expected = "Invariant violation")]
253 fn test_invariant_failure() {
254 invariant(false, "should panic");
255 }
256
257 #[test]
258 fn test_old() {
259 let x = 42;
260 let old_x = old(x);
261 assert_eq!(old_x, 42);
262 }
263
264 #[test]
265 fn test_contract_builder() {
266 let contract = Contract::new()
267 .requires(true, "x > 0")
268 .requires(true, "y > 0")
269 .ensures(true, "result > 0");
270
271 contract.check_preconditions();
272 contract.check_postconditions();
273 }
274
275 #[test]
276 #[should_panic(expected = "Precondition violation")]
277 fn test_contract_precondition_failure() {
278 let contract = Contract::new().requires(false, "x > 0");
279 contract.check_preconditions();
280 }
281
282 #[test]
283 #[should_panic(expected = "Postcondition violation")]
284 fn test_contract_postcondition_failure() {
285 let contract = Contract::new().ensures(false, "result > 0");
286 contract.check_postconditions();
287 }
288
289 #[test]
290 #[should_panic(expected = "Invariant violation")]
291 fn test_contract_invariant_failure() {
292 let contract = Contract::new().invariant(false, "count >= 0");
293 contract.check_invariants();
294 }
295
296 #[test]
297 fn test_contract_violation_display() {
298 let violation = ContractViolation::new(ContractKind::Precondition, "x > 0".to_string());
299 assert_eq!(violation.to_string(), "Precondition violation: x > 0");
300 }
301
302 #[test]
303 fn test_contract_violation_with_message() {
304 let violation = ContractViolation::new(ContractKind::Precondition, "x > 0".to_string())
305 .with_message("x must be positive".to_string());
306 assert_eq!(
307 violation.to_string(),
308 "Precondition violation: x > 0 (x must be positive)"
309 );
310 }
311}