use super::common::ObjectRef;
use crate::text::normalize_required_text;
use crate::{Confidence, CoreError, Id, Provenance, Result};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum WitnessType {
Observation,
LogEntry,
MetricPoint,
TestResult,
CodeLocation,
DocumentExcerpt,
Counterexample,
HumanReview,
MachineCheckResult,
ExternalReference,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadRef {
pub kind: String,
pub uri: String,
}
impl PayloadRef {
pub fn new(kind: impl Into<String>, uri: impl Into<String>) -> Result<Self> {
Ok(Self {
kind: normalize_required_text("payload_ref.kind", kind)?,
uri: normalize_required_text("payload_ref.uri", uri)?,
})
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum WitnessStatus {
Candidate,
Accepted,
Rejected,
Deprecated,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Witness {
pub id: Id,
pub witness_type: WitnessType,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub supports: Vec<ObjectRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub contradicts: Vec<ObjectRef>,
pub payload_ref: PayloadRef,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub validity_contexts: Vec<Id>,
pub observed_at: String,
pub provenance: Provenance,
pub confidence: Confidence,
pub review_status: WitnessStatus,
}
impl Witness {
pub fn candidate(
id: Id,
witness_type: WitnessType,
payload_ref: PayloadRef,
observed_at: impl Into<String>,
provenance: Provenance,
confidence: Confidence,
) -> Result<Self> {
Ok(Self {
id,
witness_type,
supports: Vec::new(),
contradicts: Vec::new(),
payload_ref,
validity_contexts: Vec::new(),
observed_at: normalize_required_text("observed_at", observed_at)?,
provenance,
confidence,
review_status: WitnessStatus::Candidate,
})
}
pub fn validate_acceptance(&self) -> Result<()> {
if self.validity_contexts.is_empty() {
return Err(CoreError::malformed_field(
"validity_contexts",
"accepted witness requires explicit validity context",
));
}
if matches!(self.review_status, WitnessStatus::Rejected) {
return Err(CoreError::malformed_field(
"review_status",
"rejected witness cannot be accepted support",
));
}
normalize_required_text("payload_ref.kind", &self.payload_ref.kind)?;
normalize_required_text("payload_ref.uri", &self.payload_ref.uri)?;
normalize_required_text("observed_at", &self.observed_at)?;
Ok(())
}
}