git_internal/internal/object/
evidence.rs1use std::fmt;
38
39use serde::{Deserialize, Serialize};
40use uuid::Uuid;
41
42use crate::{
43 errors::GitError,
44 hash::ObjectHash,
45 internal::object::{
46 ObjectTrait,
47 types::{ActorRef, ArtifactRef, Header, ObjectType},
48 },
49};
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(rename_all = "snake_case")]
54pub enum EvidenceKind {
55 Test,
57 Lint,
59 Build,
61 #[serde(untagged)]
62 Other(String),
63}
64
65impl fmt::Display for EvidenceKind {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 EvidenceKind::Test => write!(f, "test"),
69 EvidenceKind::Lint => write!(f, "lint"),
70 EvidenceKind::Build => write!(f, "build"),
71 EvidenceKind::Other(s) => write!(f, "{}", s),
72 }
73 }
74}
75
76impl From<String> for EvidenceKind {
77 fn from(s: String) -> Self {
78 match s.as_str() {
79 "test" => EvidenceKind::Test,
80 "lint" => EvidenceKind::Lint,
81 "build" => EvidenceKind::Build,
82 _ => EvidenceKind::Other(s),
83 }
84 }
85}
86
87impl From<&str> for EvidenceKind {
88 fn from(s: &str) -> Self {
89 match s {
90 "test" => EvidenceKind::Test,
91 "lint" => EvidenceKind::Lint,
92 "build" => EvidenceKind::Build,
93 _ => EvidenceKind::Other(s.to_string()),
94 }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct Evidence {
105 #[serde(flatten)]
107 header: Header,
108 run_id: Uuid,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
117 patchset_id: Option<Uuid>,
118 kind: EvidenceKind,
124 tool: String,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
133 command: Option<String>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
141 exit_code: Option<i32>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
148 summary: Option<String>,
149 #[serde(default, skip_serializing_if = "Vec::is_empty")]
156 report_artifacts: Vec<ArtifactRef>,
157}
158
159impl Evidence {
160 pub fn new(
161 created_by: ActorRef,
162 run_id: Uuid,
163 kind: impl Into<EvidenceKind>,
164 tool: impl Into<String>,
165 ) -> Result<Self, String> {
166 Ok(Self {
167 header: Header::new(ObjectType::Evidence, created_by)?,
168 run_id,
169 patchset_id: None,
170 kind: kind.into(),
171 tool: tool.into(),
172 command: None,
173 exit_code: None,
174 summary: None,
175 report_artifacts: Vec::new(),
176 })
177 }
178
179 pub fn header(&self) -> &Header {
180 &self.header
181 }
182
183 pub fn run_id(&self) -> Uuid {
184 self.run_id
185 }
186
187 pub fn patchset_id(&self) -> Option<Uuid> {
188 self.patchset_id
189 }
190
191 pub fn kind(&self) -> &EvidenceKind {
192 &self.kind
193 }
194
195 pub fn tool(&self) -> &str {
196 &self.tool
197 }
198
199 pub fn command(&self) -> Option<&str> {
200 self.command.as_deref()
201 }
202
203 pub fn exit_code(&self) -> Option<i32> {
204 self.exit_code
205 }
206
207 pub fn summary(&self) -> Option<&str> {
208 self.summary.as_deref()
209 }
210
211 pub fn report_artifacts(&self) -> &[ArtifactRef] {
212 &self.report_artifacts
213 }
214
215 pub fn set_patchset_id(&mut self, patchset_id: Option<Uuid>) {
216 self.patchset_id = patchset_id;
217 }
218
219 pub fn set_command(&mut self, command: Option<String>) {
220 self.command = command;
221 }
222
223 pub fn set_exit_code(&mut self, exit_code: Option<i32>) {
224 self.exit_code = exit_code;
225 }
226
227 pub fn set_summary(&mut self, summary: Option<String>) {
228 self.summary = summary;
229 }
230
231 pub fn add_report_artifact(&mut self, artifact: ArtifactRef) {
232 self.report_artifacts.push(artifact);
233 }
234}
235
236impl fmt::Display for Evidence {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 write!(f, "Evidence: {}", self.header.object_id())
239 }
240}
241
242impl ObjectTrait for Evidence {
243 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
244 where
245 Self: Sized,
246 {
247 serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
248 }
249
250 fn get_type(&self) -> ObjectType {
251 ObjectType::Evidence
252 }
253
254 fn get_size(&self) -> usize {
255 match serde_json::to_vec(self) {
256 Ok(v) => v.len(),
257 Err(e) => {
258 tracing::warn!("failed to compute Evidence size: {}", e);
259 0
260 }
261 }
262 }
263
264 fn to_data(&self) -> Result<Vec<u8>, GitError> {
265 serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_evidence_fields() {
275 let actor = ActorRef::agent("test-agent").expect("actor");
276 let run_id = Uuid::from_u128(0x1);
277 let patchset_id = Uuid::from_u128(0x2);
278
279 let mut evidence = Evidence::new(actor, run_id, "test", "cargo").expect("evidence");
280 evidence.set_patchset_id(Some(patchset_id));
281 evidence.set_exit_code(Some(1));
282 evidence.add_report_artifact(ArtifactRef::new("local", "log.txt").expect("artifact"));
283
284 assert_eq!(evidence.patchset_id(), Some(patchset_id));
285 assert_eq!(evidence.exit_code(), Some(1));
286 assert_eq!(evidence.report_artifacts().len(), 1);
287 assert_eq!(evidence.kind(), &EvidenceKind::Test);
288 }
289}