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