1use crate::aql::{Aql, AqlBuildError};
2use crate::http::path;
3use crate::types::{EvidenceResponse, NumericConflictResponse, VerificationReportResponse};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum VerifyOutputFormat {
7 Json,
8 Markdown,
9 Audit,
10}
11
12impl VerifyOutputFormat {
13 pub fn as_str(self) -> &'static str {
14 match self {
15 Self::Json => "json",
16 Self::Markdown => "markdown",
17 Self::Audit => "audit",
18 }
19 }
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct VerifyRequest {
24 scope: String,
25 statement: String,
26 format: VerifyOutputFormat,
27}
28
29impl VerifyRequest {
30 pub fn new(scope: impl Into<String>, statement: impl Into<String>) -> Self {
31 Self {
32 scope: scope.into(),
33 statement: statement.into(),
34 format: VerifyOutputFormat::Json,
35 }
36 }
37
38 pub fn fact(
39 scope: impl Into<String>,
40 fact: impl Into<String>,
41 brain: impl Into<String>,
42 ) -> Result<Self, AqlBuildError> {
43 let statement = Aql::verify_fact(fact, brain).build()?;
44 Ok(Self::new(scope, statement))
45 }
46
47 pub fn json(mut self) -> Self {
48 self.format = VerifyOutputFormat::Json;
49 self
50 }
51
52 pub fn markdown(mut self) -> Self {
53 self.format = VerifyOutputFormat::Markdown;
54 self
55 }
56
57 pub fn audit(mut self) -> Self {
58 self.format = VerifyOutputFormat::Audit;
59 self
60 }
61
62 pub fn with_format(mut self, format: VerifyOutputFormat) -> Self {
63 self.format = format;
64 self
65 }
66
67 pub fn scope(&self) -> &str {
68 &self.scope
69 }
70
71 pub fn statement(&self) -> &str {
72 &self.statement
73 }
74
75 pub fn format(&self) -> VerifyOutputFormat {
76 self.format
77 }
78
79 pub fn path(&self) -> String {
80 path(
81 "/v1/verify",
82 &[
83 ("scope", self.scope.as_str()),
84 ("format", self.format.as_str()),
85 ],
86 )
87 }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub enum VerifyResult {
92 Supported,
93 Insufficient,
94 Contradicted,
95 MixedEvidence,
96 Unknown(String),
97}
98
99impl VerifyResult {
100 pub fn from_wire(value: &str) -> Self {
101 match value {
102 "supported" => Self::Supported,
103 "insufficient" => Self::Insufficient,
104 "contradicted" => Self::Contradicted,
105 "mixed" | "mixed_evidence" => Self::MixedEvidence,
106 other => Self::Unknown(other.to_owned()),
107 }
108 }
109
110 pub fn as_str(&self) -> &str {
111 match self {
112 Self::Supported => "supported",
113 Self::Insufficient => "insufficient",
114 Self::Contradicted => "contradicted",
115 Self::MixedEvidence => "mixed_evidence",
116 Self::Unknown(value) => value.as_str(),
117 }
118 }
119
120 pub fn is_actionable_conflict(&self) -> bool {
121 matches!(self, Self::Contradicted | Self::MixedEvidence)
122 }
123}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
126pub enum VerifyConflict {
127 ContradictingEvidence(VerifyEvidenceConflict),
128 Numeric(VerifyNumericConflict),
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub struct VerifyEvidenceConflict {
133 pub cell_id: u64,
134 pub matched_terms: u32,
135 pub match_score_q16: u16,
136 pub match_kind: String,
137 pub source_trust_q16: u16,
138 pub source_trust_category: String,
139 pub citation: Option<String>,
140 pub payload_text: String,
141}
142
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct VerifyNumericConflict {
145 pub metric: String,
146 pub left: String,
147 pub right: String,
148}
149
150impl From<EvidenceResponse> for VerifyEvidenceConflict {
151 fn from(value: EvidenceResponse) -> Self {
152 Self {
153 cell_id: value.cell_id,
154 matched_terms: value.matched_terms,
155 match_score_q16: value.match_score_q16,
156 match_kind: value.match_kind,
157 source_trust_q16: value.source_trust_q16,
158 source_trust_category: value.source_trust_category,
159 citation: value.citation,
160 payload_text: value.payload_text,
161 }
162 }
163}
164
165impl From<NumericConflictResponse> for VerifyNumericConflict {
166 fn from(value: NumericConflictResponse) -> Self {
167 Self {
168 metric: value.metric,
169 left: value.left,
170 right: value.right,
171 }
172 }
173}
174
175pub trait VerificationReportExt {
176 fn result(&self) -> VerifyResult;
177 fn has_conflicts(&self) -> bool;
178 fn conflicts(&self) -> Vec<VerifyConflict>;
179}
180
181impl VerificationReportExt for VerificationReportResponse {
182 fn result(&self) -> VerifyResult {
183 let status = VerifyResult::from_wire(&self.status);
184 match status {
185 VerifyResult::Unknown(_) => VerifyResult::from_wire(&self.verdict),
186 known => known,
187 }
188 }
189
190 fn has_conflicts(&self) -> bool {
191 !self.contradicting_evidence.is_empty() || !self.numeric_conflicts.is_empty()
192 }
193
194 fn conflicts(&self) -> Vec<VerifyConflict> {
195 let evidence = self
196 .contradicting_evidence
197 .iter()
198 .cloned()
199 .map(VerifyEvidenceConflict::from)
200 .map(VerifyConflict::ContradictingEvidence);
201 let numeric = self
202 .numeric_conflicts
203 .iter()
204 .cloned()
205 .map(VerifyNumericConflict::from)
206 .map(VerifyConflict::Numeric);
207
208 evidence.chain(numeric).collect()
209 }
210}