1use crate::{
9 capability::fact_private_capability,
10 claim::{Claim, ClaimKind, Visibility},
11 datum::Datum,
12 datum_store::DatumStore,
13 env::Cx,
14 error::{Diagnostic, Error, Result, Severity},
15 expr::{Expr, NumberLiteral, Span},
16 hint::HintMetadata,
17 id::Symbol,
18 object::ShapeRef,
19 ref_id::{ContentId, Coordinate, HandleId, Ref},
20 ref_resolver::{RefResolver, TemporaryRefResolver},
21 shape::{MatchScore, ShapeBindings, ShapeMatch},
22 value::Value,
23};
24
25#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct ShapeReport {
34 pub id: Ref,
36 pub shape: Ref,
38 pub target: Ref,
40 pub accepted: bool,
42 pub score: MatchScore,
44 pub captures: Ref,
46 pub diagnostics: Vec<Diagnostic>,
48}
49
50impl ShapeReport {
51 pub fn canonical_datum(&self) -> Datum {
53 shape_report_datum(
54 &self.shape,
55 &self.target,
56 self.accepted,
57 self.score,
58 &self.captures,
59 &self.diagnostics,
60 )
61 }
62}
63
64pub fn check_value_report(
70 cx: &mut Cx,
71 shape_value: &ShapeRef,
72 value: Value,
73) -> Result<ShapeReport> {
74 let shape_ref = ref_for_shape(cx, shape_value)?;
75 let mut resolver = TemporaryRefResolver::new();
76 let target_ref = resolver.ref_for_value(cx, &value)?;
77 let visibility = satisfaction_visibility(cx, &target_ref, &shape_ref, &value)?;
78
79 let Some(shape) = shape_value.object().as_shape() else {
80 return Err(Error::TypeMismatch {
81 expected: "shape",
82 found: "non-shape",
83 });
84 };
85 let matched = shape.check_value(cx, value)?;
86 let report = shape_report_from_match(cx, shape_ref, target_ref, matched)?;
87 insert_shape_satisfaction_claim(cx, &report, visibility)?;
88 Ok(report)
89}
90
91pub fn shape_report_from_match(
94 cx: &mut Cx,
95 shape: Ref,
96 target: Ref,
97 matched: ShapeMatch,
98) -> Result<ShapeReport> {
99 let captures = captures_ref(cx, &matched.captures)?;
100 let datum = shape_report_datum(
101 &shape,
102 &target,
103 matched.accepted,
104 matched.score,
105 &captures,
106 &matched.diagnostics,
107 );
108 let id = cx.datum_store_mut().intern(datum)?;
109 Ok(ShapeReport {
110 id: Ref::Content(id),
111 shape,
112 target,
113 accepted: matched.accepted,
114 score: matched.score,
115 captures,
116 diagnostics: matched.diagnostics,
117 })
118}
119
120pub fn insert_shape_satisfaction_claim(
125 cx: &mut Cx,
126 report: &ShapeReport,
127 visibility: Visibility,
128) -> Result<Option<Ref>> {
129 if !report.accepted {
130 return Ok(None);
131 }
132
133 let claim = Claim::public(
134 report.target.clone(),
135 satisfies_shape_predicate(),
136 report.shape.clone(),
137 )
138 .with_kind(ClaimKind::Observed)
139 .with_evidence(vec![report.id.clone()])
140 .with_visibility(visibility);
141
142 if visibility == Visibility::Private {
143 let mut capabilities = cx.capabilities().clone();
144 capabilities.insert(fact_private_capability());
145 cx.with_capabilities(capabilities, |cx| cx.insert_fact(claim))
146 .map(Some)
147 } else {
148 cx.insert_fact(claim).map(Some)
149 }
150}
151
152pub fn satisfies_shape_predicate() -> Symbol {
154 core_symbol("satisfies-shape")
155}
156
157fn ref_for_shape(cx: &mut Cx, shape_value: &ShapeRef) -> Result<Ref> {
158 if let Some(symbol) = shape_value
159 .object()
160 .as_shape()
161 .and_then(|shape| shape.symbol())
162 {
163 return Ok(Ref::Symbol(symbol));
164 }
165 TemporaryRefResolver::new().ref_for_value(cx, shape_value)
166}
167
168fn satisfaction_visibility(
169 cx: &mut Cx,
170 target: &Ref,
171 shape: &Ref,
172 value: &Value,
173) -> Result<Visibility> {
174 if matches!(target, Ref::Handle(_))
175 && !value
176 .object()
177 .publish_shape_satisfaction_claims(cx, shape)?
178 {
179 return Ok(Visibility::Private);
180 }
181 Ok(Visibility::Public)
182}
183
184fn captures_ref(cx: &mut Cx, captures: &ShapeBindings) -> Result<Ref> {
185 let datum = captures_datum(cx, captures)?;
186 cx.datum_store_mut().intern(datum).map(Ref::Content)
187}
188
189fn captures_datum(cx: &mut Cx, captures: &ShapeBindings) -> Result<Datum> {
190 let mut resolver = TemporaryRefResolver::new();
191 let values = captures
192 .values()
193 .iter()
194 .map(|(name, value)| {
195 let value_ref = resolver.ref_for_value(cx, value)?;
196 Ok(binding_datum(name, "value", ref_datum(&value_ref)))
197 })
198 .collect::<Result<Vec<_>>>()?;
199 let exprs = captures
200 .exprs()
201 .iter()
202 .map(|(name, expr)| binding_datum(name, "expr", expr_datum(expr)))
203 .collect();
204
205 Ok(Datum::Node {
206 tag: core_symbol("ShapeCaptures"),
207 fields: vec![
208 (Symbol::new("values"), Datum::Vector(values)),
209 (Symbol::new("exprs"), Datum::Vector(exprs)),
210 ],
211 })
212}
213
214fn binding_datum(name: &Symbol, kind: &str, value: Datum) -> Datum {
215 Datum::Node {
216 tag: core_symbol("shape-binding"),
217 fields: vec![
218 (Symbol::new("name"), Datum::Symbol(name.clone())),
219 (Symbol::new("kind"), Datum::Symbol(core_symbol(kind))),
220 (Symbol::new("value"), value),
221 ],
222 }
223}
224
225fn expr_datum(expr: &Expr) -> Datum {
226 Datum::try_from(expr.clone()).unwrap_or_else(|_| Datum::Node {
227 tag: core_symbol("expr-canonical-key"),
228 fields: vec![(Symbol::new("debug"), Datum::String(format!("{:?}", expr)))],
229 })
230}
231
232fn shape_report_datum(
233 shape: &Ref,
234 target: &Ref,
235 accepted: bool,
236 score: MatchScore,
237 captures: &Ref,
238 diagnostics: &[Diagnostic],
239) -> Datum {
240 Datum::Node {
241 tag: core_symbol("ShapeReport"),
242 fields: vec![
243 (Symbol::new("shape"), ref_datum(shape)),
244 (Symbol::new("target"), ref_datum(target)),
245 (Symbol::new("accepted"), Datum::Bool(accepted)),
246 (Symbol::new("score"), score_datum(score)),
247 (Symbol::new("captures"), ref_datum(captures)),
248 (
249 Symbol::new("diagnostics"),
250 Datum::Vector(diagnostics.iter().map(diagnostic_datum).collect()),
251 ),
252 ],
253 }
254}
255
256fn diagnostic_datum(diagnostic: &Diagnostic) -> Datum {
257 let hints = HintMetadata::collect_from_diagnostic(diagnostic)
258 .into_iter()
259 .map(|hint| hint.as_datum())
260 .collect();
261 let mut fields = vec![
262 (
263 Symbol::new("severity"),
264 Datum::Symbol(severity_symbol(diagnostic.severity)),
265 ),
266 (
267 Symbol::new("message"),
268 Datum::String(diagnostic.message.clone()),
269 ),
270 (
271 Symbol::new("related"),
272 Datum::Vector(
273 diagnostic
274 .related
275 .iter()
276 .filter(|related| !HintMetadata::is_hint_diagnostic(related))
277 .map(diagnostic_datum)
278 .collect(),
279 ),
280 ),
281 (Symbol::new("hints"), Datum::Vector(hints)),
282 ];
283 if let Some(source) = &diagnostic.source {
284 fields.push((Symbol::new("source"), Datum::String(source.0.clone())));
285 }
286 if let Some(span) = &diagnostic.span {
287 fields.push((Symbol::new("span"), span_datum(span)));
288 }
289 if let Some(code) = &diagnostic.code {
290 fields.push((Symbol::new("code"), Datum::Symbol(code.clone())));
291 }
292 Datum::Node {
293 tag: core_symbol("diagnostic"),
294 fields,
295 }
296}
297
298fn span_datum(span: &Span) -> Datum {
299 Datum::Node {
300 tag: core_symbol("span"),
301 fields: vec![
302 (
303 Symbol::new("start"),
304 Datum::Number(usize_number(span.start)),
305 ),
306 (Symbol::new("end"), Datum::Number(usize_number(span.end))),
307 ],
308 }
309}
310
311fn severity_symbol(severity: Severity) -> Symbol {
312 match severity {
313 Severity::Error => core_symbol("error"),
314 Severity::Warning => core_symbol("warning"),
315 Severity::Info => core_symbol("info"),
316 Severity::Note => core_symbol("note"),
317 }
318}
319
320fn score_datum(score: MatchScore) -> Datum {
321 Datum::Number(NumberLiteral {
322 domain: Symbol::qualified("numbers", "f64"),
323 canonical: score.value().to_string(),
324 })
325}
326
327fn usize_number(value: usize) -> NumberLiteral {
328 NumberLiteral {
329 domain: Symbol::qualified("numbers", "i64"),
330 canonical: value.to_string(),
331 }
332}
333
334fn ref_datum(reference: &Ref) -> Datum {
335 match reference {
336 Ref::Symbol(symbol) => Datum::Node {
337 tag: core_symbol("ref"),
338 fields: vec![
339 (Symbol::new("kind"), Datum::Symbol(core_symbol("symbol"))),
340 (Symbol::new("symbol"), Datum::Symbol(symbol.clone())),
341 ],
342 },
343 Ref::Content(content) => Datum::Node {
344 tag: core_symbol("ref"),
345 fields: vec![
346 (Symbol::new("kind"), Datum::Symbol(core_symbol("content"))),
347 (Symbol::new("content"), content_id_datum(content)),
348 ],
349 },
350 Ref::Handle(handle) => Datum::Node {
351 tag: core_symbol("ref"),
352 fields: vec![
353 (Symbol::new("kind"), Datum::Symbol(core_symbol("handle"))),
354 (Symbol::new("id"), handle_id_datum(*handle)),
355 ],
356 },
357 Ref::Coord(coordinate) => coordinate_datum(coordinate),
358 }
359}
360
361fn coordinate_datum(coordinate: &Coordinate) -> Datum {
362 Datum::Node {
363 tag: core_symbol("ref"),
364 fields: vec![
365 (Symbol::new("kind"), Datum::Symbol(core_symbol("coord"))),
366 (
367 Symbol::new("space"),
368 Datum::Symbol(coordinate.space.clone()),
369 ),
370 (
371 Symbol::new("ordinal"),
372 content_id_datum(&coordinate.ordinal),
373 ),
374 ],
375 }
376}
377
378fn content_id_datum(content: &ContentId) -> Datum {
379 Datum::Node {
380 tag: core_symbol("content-id"),
381 fields: vec![
382 (
383 Symbol::new("algorithm"),
384 Datum::Symbol(content.algorithm.clone()),
385 ),
386 (Symbol::new("bytes"), Datum::Bytes(content.bytes.to_vec())),
387 ],
388 }
389}
390
391fn handle_id_datum(handle: HandleId) -> Datum {
392 Datum::Bytes(handle.0.to_be_bytes().to_vec())
393}
394
395fn core_symbol(name: &str) -> Symbol {
396 Symbol::qualified("core", name)
397}
398
399#[cfg(test)]
400#[path = "shape_report/tests.rs"]
401mod tests;