use serde::{Deserialize, Serialize};
use crate::ids::CardUid;
use crate::origin::Origin;
use crate::reference::CardRef;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct TimeRange {
pub start: chrono::DateTime<chrono::Utc>,
pub end: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, schemars::JsonSchema)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct Lineage {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub card_uid: Option<CardUid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub card_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub origin: Option<Origin>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time_range: Option<TimeRange>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub source_refs: Vec<CardRef>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct Evidence {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claim: Option<String>,
#[schemars(with = "serde_json::Value")]
#[cfg_attr(feature = "server", schema(value_type = serde_json::Value))]
pub data: serde_json::Value,
#[serde(default)]
pub lineage: Lineage,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::origin::CommitSha;
#[test]
fn evidence_round_trips_and_omits_empty_lineage_fields() {
let evidence = Evidence {
claim: Some("tool calls time out at 3% p99".to_string()),
data: serde_json::json!({ "p99_ms": 5200, "timeout_rate": 0.03 }),
lineage: Lineage {
origin: Some(Origin {
repo: "github.com/org/repo".to_string(),
commit: CommitSha::new("deadbeef").expect("valid commit"),
path: None,
dirty: false,
}),
query: Some("SELECT ...".to_string()),
..Lineage::default()
},
};
let json = serde_json::to_value(&evidence).expect("serialize");
assert_eq!(json["lineage"].get("card_uid"), None);
assert_eq!(json["lineage"].get("source_refs"), None);
let back: Evidence = serde_json::from_value(json).expect("deserialize");
assert_eq!(evidence, back);
}
#[test]
fn evidence_defaults_lineage_when_absent() {
let json = serde_json::json!({ "data": { "ok": true } });
let evidence: Evidence = serde_json::from_value(json).expect("deserialize");
assert_eq!(evidence.lineage, Lineage::default());
assert!(evidence.claim.is_none());
}
#[test]
fn schema_generates() {
let _ = schemars::schema_for!(Evidence);
}
}