use std::fmt;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
errors::GitError,
hash::ObjectHash,
internal::object::{
ObjectTrait,
types::{ActorRef, ArtifactRef, Header, ObjectType},
},
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceKind {
Test,
Lint,
Build,
#[serde(untagged)]
Other(String),
}
impl fmt::Display for EvidenceKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EvidenceKind::Test => write!(f, "test"),
EvidenceKind::Lint => write!(f, "lint"),
EvidenceKind::Build => write!(f, "build"),
EvidenceKind::Other(s) => write!(f, "{}", s),
}
}
}
impl From<String> for EvidenceKind {
fn from(s: String) -> Self {
match s.as_str() {
"test" => EvidenceKind::Test,
"lint" => EvidenceKind::Lint,
"build" => EvidenceKind::Build,
_ => EvidenceKind::Other(s),
}
}
}
impl From<&str> for EvidenceKind {
fn from(s: &str) -> Self {
match s {
"test" => EvidenceKind::Test,
"lint" => EvidenceKind::Lint,
"build" => EvidenceKind::Build,
_ => EvidenceKind::Other(s.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Evidence {
#[serde(flatten)]
header: Header,
run_id: Uuid,
#[serde(default, skip_serializing_if = "Option::is_none")]
patchset_id: Option<Uuid>,
kind: EvidenceKind,
tool: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
exit_code: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
summary: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
report_artifacts: Vec<ArtifactRef>,
}
impl Evidence {
pub fn new(
created_by: ActorRef,
run_id: Uuid,
kind: impl Into<EvidenceKind>,
tool: impl Into<String>,
) -> Result<Self, String> {
Ok(Self {
header: Header::new(ObjectType::Evidence, created_by)?,
run_id,
patchset_id: None,
kind: kind.into(),
tool: tool.into(),
command: None,
exit_code: None,
summary: None,
report_artifacts: Vec::new(),
})
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn run_id(&self) -> Uuid {
self.run_id
}
pub fn patchset_id(&self) -> Option<Uuid> {
self.patchset_id
}
pub fn kind(&self) -> &EvidenceKind {
&self.kind
}
pub fn tool(&self) -> &str {
&self.tool
}
pub fn command(&self) -> Option<&str> {
self.command.as_deref()
}
pub fn exit_code(&self) -> Option<i32> {
self.exit_code
}
pub fn summary(&self) -> Option<&str> {
self.summary.as_deref()
}
pub fn report_artifacts(&self) -> &[ArtifactRef] {
&self.report_artifacts
}
pub fn set_patchset_id(&mut self, patchset_id: Option<Uuid>) {
self.patchset_id = patchset_id;
}
pub fn set_command(&mut self, command: Option<String>) {
self.command = command;
}
pub fn set_exit_code(&mut self, exit_code: Option<i32>) {
self.exit_code = exit_code;
}
pub fn set_summary(&mut self, summary: Option<String>) {
self.summary = summary;
}
pub fn add_report_artifact(&mut self, artifact: ArtifactRef) {
self.report_artifacts.push(artifact);
}
}
impl fmt::Display for Evidence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Evidence: {}", self.header.object_id())
}
}
impl ObjectTrait for Evidence {
fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
where
Self: Sized,
{
serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
}
fn get_type(&self) -> ObjectType {
ObjectType::Evidence
}
fn get_size(&self) -> usize {
match serde_json::to_vec(self) {
Ok(v) => v.len(),
Err(e) => {
tracing::warn!("failed to compute Evidence size: {}", e);
0
}
}
}
fn to_data(&self) -> Result<Vec<u8>, GitError> {
serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_evidence_fields() {
let actor = ActorRef::agent("test-agent").expect("actor");
let run_id = Uuid::from_u128(0x1);
let patchset_id = Uuid::from_u128(0x2);
let mut evidence = Evidence::new(actor, run_id, "test", "cargo").expect("evidence");
evidence.set_patchset_id(Some(patchset_id));
evidence.set_exit_code(Some(1));
evidence.add_report_artifact(ArtifactRef::new("local", "log.txt").expect("artifact"));
assert_eq!(evidence.patchset_id(), Some(patchset_id));
assert_eq!(evidence.exit_code(), Some(1));
assert_eq!(evidence.report_artifacts().len(), 1);
assert_eq!(evidence.kind(), &EvidenceKind::Test);
}
}