rustev_eval/explain/
mod.rs1mod bounds;
3mod diff;
4mod render;
5pub use diff::explain_diff;
6pub use render::render_markdown;
7mod evidence;
8mod excluded;
9mod facts;
10mod models;
11mod order;
12mod outcome;
13mod redact;
14mod scopes;
15mod selected;
16mod text;
17
18use crate::resolve::Resolver;
19use rustev_contract::explanation::*;
20use rustev_contract::redaction::RedactionPolicy;
21use rustev_contract::scope::Scope;
22use rustev_contract::time::Timestamp;
23
24pub enum Evidence<'a> {
25 Record {
26 record: &'a [u8],
27 plan: Option<&'a [u8]>,
28 },
29 Bundle {
30 bundle: &'a [u8],
31 scope: &'a Scope,
32 now_ms: Timestamp,
33 resolver: &'a dyn Resolver,
34 },
35}
36#[derive(Default)]
37pub struct ExplainOptions<'a> {
38 pub redaction: Option<&'a RedactionPolicy>,
39}
40#[derive(Debug)]
41pub struct ExplainError {
42 pub code: RefusalCode,
43 pub subject: String,
44 pub detail: String,
45}
46impl std::fmt::Display for ExplainError {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 write!(
49 f,
50 "{}: {}: {}",
51 self.code.as_str(),
52 self.subject,
53 self.detail
54 )
55 }
56}
57impl std::error::Error for ExplainError {}
58impl ExplainError {
59 fn new(code: RefusalCode, subject: &str, detail: impl ToString) -> Self {
60 Self {
61 code,
62 subject: subject.into(),
63 detail: detail.to_string(),
64 }
65 }
66 fn parse(subject: &str, e: impl ToString) -> Self {
67 Self::new(RefusalCode::Parse, subject, e)
68 }
69}
70pub fn explain(
71 evidence: &Evidence<'_>,
72 query: &Query,
73 options: &ExplainOptions<'_>,
74) -> Result<Explanation, ExplainError> {
75 query
76 .validate()
77 .map_err(|c| ExplainError::new(c, "query", "invalid query"))?;
78 if matches!(query, Query::Diff) {
79 return Err(diff_requires_pair());
80 }
81 if let Some(p) = options.redaction {
82 p.validate()
83 .map_err(|e| ExplainError::parse("redaction", e))?;
84 }
85 let evidence = evidence::Set::read(evidence)?;
86 bounds::build(&evidence, query, options, bounds::Bounds::default())
87}
88
89fn dispatch(b: &mut facts::Builder<'_>, query: &Query) -> Result<Answer, ExplainError> {
90 match query {
91 Query::WhyOutcome => outcome::build(b),
92 Query::TextOrigin => text::build(b),
93 Query::Models => models::build(b),
94 Query::WhySelected { item } => selected::build(b, item),
95 Query::WhyExcluded {
96 item: Item::String(item),
97 } => excluded::build(b, item),
98 Query::WhyOrder {
99 a: Item::String(a),
100 b: Item::String(c),
101 } => order::build(b, a, c),
102 Query::Diff => Err(diff_requires_pair()),
103 Query::WhyExcluded { .. } | Query::WhyOrder { .. } => Err(ExplainError::new(
106 RefusalCode::InvalidQuery,
107 "query",
108 "query items must be strings",
109 )),
110 }
111}
112
113fn diff_requires_pair() -> ExplainError {
114 ExplainError::new(
115 RefusalCode::InvalidQuery,
116 "query",
117 "use explain_diff with two evidence sets",
118 )
119}