Skip to main content

objects/object/
state_verification.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Verification metadata for states.
3
4use std::collections::BTreeMap;
5
6use serde::{Deserialize, Serialize};
7
8/// Verification information for a state.
9#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
10pub struct Verification {
11    /// Whether tests passed.
12    pub tests_passed: Option<bool>,
13    /// Number of failed tests.
14    pub tests_failed: Option<u32>,
15    /// Test coverage percentage.
16    pub coverage_pct: Option<f32>,
17    /// Change in coverage from parent.
18    pub coverage_delta: Option<f32>,
19    /// Number of lint warnings.
20    pub lint_warnings: Option<u32>,
21    /// Custom verification data.
22    #[serde(default)]
23    pub custom: BTreeMap<String, serde_json::Value>,
24}
25
26impl Verification {
27    /// Create empty verification.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Set tests passed.
33    pub fn with_tests_passed(mut self, passed: bool) -> Self {
34        self.tests_passed = Some(passed);
35        self
36    }
37
38    /// Set test failures.
39    pub fn with_tests_failed(mut self, failed: u32) -> Self {
40        self.tests_failed = Some(failed);
41        self
42    }
43
44    /// Check if any verification data is present.
45    pub fn is_empty(&self) -> bool {
46        self.tests_passed.is_none()
47            && self.tests_failed.is_none()
48            && self.coverage_pct.is_none()
49            && self.coverage_delta.is_none()
50            && self.lint_warnings.is_none()
51            && self.custom.is_empty()
52    }
53
54    pub(crate) fn hash_len(&self) -> usize {
55        let mut len = 0;
56
57        len += 1 + self.tests_passed.map(|_| 1).unwrap_or(0);
58        len += 1 + self.tests_failed.map(|_| 4).unwrap_or(0);
59        len += 1 + self.coverage_pct.map(|_| 4).unwrap_or(0);
60        len += 1 + self.coverage_delta.map(|_| 4).unwrap_or(0);
61        len += 1 + self.lint_warnings.map(|_| 4).unwrap_or(0);
62
63        len += 4;
64        for (key, value) in &self.custom {
65            let value_bytes = serde_json::to_vec(value).unwrap_or_default();
66            len += 4 + key.len();
67            len += 4 + value_bytes.len();
68        }
69
70        len
71    }
72
73    pub(crate) fn update_hasher(&self, hasher: &mut blake3::Hasher) {
74        let tests_passed = self.tests_passed.map(u8::from);
75        write_optional_u8(hasher, tests_passed);
76        write_optional_u32(hasher, self.tests_failed);
77        write_optional_f32(hasher, self.coverage_pct);
78        write_optional_f32(hasher, self.coverage_delta);
79        write_optional_u32(hasher, self.lint_warnings);
80
81        let custom_len = self.custom.len() as u32;
82        hasher.update(&custom_len.to_le_bytes());
83        for (key, value) in &self.custom {
84            let key_bytes = key.as_bytes();
85            let value_bytes = serde_json::to_vec(value).unwrap_or_default();
86
87            hasher.update(&(key_bytes.len() as u32).to_le_bytes());
88            hasher.update(key_bytes);
89
90            hasher.update(&(value_bytes.len() as u32).to_le_bytes());
91            hasher.update(&value_bytes);
92        }
93    }
94}
95
96fn write_optional_u8(hasher: &mut blake3::Hasher, value: Option<u8>) {
97    match value {
98        Some(v) => {
99            hasher.update(&[1]);
100            hasher.update(&[v]);
101        }
102        None => {
103            hasher.update(&[0]);
104        }
105    }
106}
107
108fn write_optional_u32(hasher: &mut blake3::Hasher, value: Option<u32>) {
109    match value {
110        Some(v) => {
111            hasher.update(&[1]);
112            hasher.update(&v.to_le_bytes());
113        }
114        None => {
115            hasher.update(&[0]);
116        }
117    }
118}
119
120fn write_optional_f32(hasher: &mut blake3::Hasher, value: Option<f32>) {
121    match value {
122        Some(v) => {
123            hasher.update(&[1]);
124            hasher.update(&v.to_le_bytes());
125        }
126        None => {
127            hasher.update(&[0]);
128        }
129    }
130}