use crate::prove::{BadRuleApplication, RuleApplication};
use crate::rule::Rule;
use alloc::collections::BTreeSet;
pub fn validate<Unbound: Ord + Clone, Bound: Ord + Clone>(
rules: &[Rule<Unbound, Bound>],
proof: &[RuleApplication<Bound>],
) -> Result<Valid<Bound>, Invalid> {
let mut implied: BTreeSet<[Bound; 4]> = BTreeSet::new();
let mut assumed: BTreeSet<[Bound; 4]> = BTreeSet::new();
for app in proof {
let rule = rules.get(app.rule_index).ok_or(Invalid::NoSuchRule)?;
for assumption in app.assumptions_when_applied(rule)? {
if !implied.contains(&assumption) {
assumed.insert(assumption);
}
}
for implication in app.implications_when_applied(rule)? {
if !assumed.contains(&implication) {
implied.insert(implication);
}
}
}
debug_assert!(assumed.is_disjoint(&implied));
Ok(Valid { assumed, implied })
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Valid<Bound> {
#[cfg_attr(
feature = "serde",
serde(bound(serialize = "Bound: Ord", deserialize = "Bound: Ord"))
)]
pub assumed: BTreeSet<[Bound; 4]>,
pub implied: BTreeSet<[Bound; 4]>,
}
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Invalid {
BadRuleApplication,
NoSuchRule,
}
impl From<BadRuleApplication> for Invalid {
fn from(_: BadRuleApplication) -> Self {
Self::BadRuleApplication
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::common::decl_rules;
use crate::prove::prove;
use crate::rule::Entity::{Bound as B, Unbound as U};
#[test]
fn irrelevant_facts_ignored() {
let facts: Vec<[&str; 4]> = vec![
["tacos", "are", "tasty", "default_graph"],
["nachos", "are", "tasty", "default_graph"],
];
let rules = decl_rules::<&str, &str>(&[[
&[[B("nachos"), B("are"), B("tasty"), B("default_graph")]],
&[[B("nachos"), B("are"), B("food"), B("default_graph")]],
]]);
let composite_claims = vec![["nachos", "are", "food", "default_graph"]];
let proof = prove::<&str, &str>(&facts, &composite_claims, &rules).unwrap();
let Valid { assumed, implied } = validate::<&str, &str>(&rules, &proof).unwrap();
assert_eq!(
&assumed,
&[["nachos", "are", "tasty", "default_graph"]]
.iter()
.cloned()
.collect()
);
for claim in composite_claims {
assert!(implied.contains(&claim));
}
}
#[test]
fn bad_rule_application() {
let facts: Vec<[&str; 4]> = vec![["a", "a", "a", "a"]];
let rules_v1 = decl_rules::<&str, &str>(&[[
&[[U("a"), B("a"), B("a"), B("a")]],
&[[B("b"), B("b"), B("b"), B("b")]],
]]);
let rules_v2 = decl_rules::<&str, &str>(&[[
&[[B("a"), B("a"), B("a"), B("a")]],
&[[B("b"), B("b"), B("b"), B("b")]],
]]);
let composite_claims = vec![["b", "b", "b", "b"]];
let proof = prove::<&str, &str>(&facts, &composite_claims, &rules_v1).unwrap();
let err = validate::<&str, &str>(&rules_v2, &proof).unwrap_err();
assert_eq!(err, Invalid::BadRuleApplication);
}
#[test]
fn no_such_rule() {
let facts: Vec<[&str; 4]> = vec![["a", "a", "a", "a"]];
let rules = decl_rules::<&str, &str>(&[[
&[[B("a"), B("a"), B("a"), B("a")]],
&[[B("b"), B("b"), B("b"), B("b")]],
]]);
let composite_claims = vec![["b", "b", "b", "b"]];
let proof = prove::<&str, &str>(&facts, &composite_claims, &rules).unwrap();
let err = validate::<&str, &str>(&[], &proof).unwrap_err();
assert_eq!(err, Invalid::NoSuchRule);
}
#[test]
fn validate_manual_proof() {
let rules = decl_rules(&[
[
&[
[B("andrew"), B("claims"), U("c"), B("default_graph")],
[U("c"), B("subject"), U("s"), B("default_graph")],
[U("c"), B("property"), U("p"), B("default_graph")],
[U("c"), B("object"), U("o"), B("default_graph")],
],
&[[U("s"), U("p"), U("o"), B("default_graph")]],
],
[
&[[U("a"), B("favoriteFood"), U("f"), B("default_graph")]],
&[
[U("a"), B("likes"), U("f"), B("default_graph")],
[U("f"), B("type"), B("food"), B("default_graph")],
],
],
[
&[
[U("f"), B("type"), B("food"), B("default_graph")],
[U("a"), B("alergyFree"), B("true"), B("default_graph")],
],
&[[U("a"), B("mayEat"), U("f"), B("default_graph")]],
],
]);
let facts: &[[&str; 4]] = &[
["alice", "favoriteFood", "beans", "default_graph"],
["andrew", "claims", "_:claim1", "default_graph"],
["_:claim1", "subject", "bob", "default_graph"],
["_:claim1", "property", "alergyFree", "default_graph"],
["_:claim1", "object", "true", "default_graph"],
];
let manual_proof = decl_proof(&[
(1, &["alice", "beans"]),
(0, &["_:claim1", "bob", "alergyFree", "true"]),
(2, &["beans", "bob"]),
]);
let Valid { assumed, implied } = validate(&rules, &manual_proof).unwrap();
assert_eq!(assumed, facts.iter().cloned().collect());
assert_eq!(
implied,
[
["alice", "likes", "beans", "default_graph"],
["beans", "type", "food", "default_graph"],
["bob", "alergyFree", "true", "default_graph"],
["bob", "mayEat", "beans", "default_graph"] ]
.iter()
.cloned()
.collect()
);
}
fn decl_proof<'a>(ep: &'a [(usize, &[&str])]) -> Vec<RuleApplication<&'a str>> {
ep.iter()
.map(|(rule_index, inst)| RuleApplication {
rule_index: *rule_index,
instantiations: inst.to_vec(),
})
.collect()
}
}