use aingle_graph::{NodeId, Predicate, Triple, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Rule {
pub id: String,
pub name: String,
pub description: String,
pub kind: RuleKind,
pub conditions: Vec<Condition>,
pub action: Action,
pub priority: i32,
pub enabled: bool,
}
impl Rule {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
description: String::new(),
kind: RuleKind::Integrity,
conditions: Vec::new(),
action: Action::Accept,
priority: 0,
enabled: true,
}
}
pub fn integrity(id: impl Into<String>) -> RuleBuilder {
RuleBuilder::new(id).kind(RuleKind::Integrity)
}
pub fn authority(id: impl Into<String>) -> RuleBuilder {
RuleBuilder::new(id).kind(RuleKind::Authority)
}
pub fn temporal(id: impl Into<String>) -> RuleBuilder {
RuleBuilder::new(id).kind(RuleKind::Temporal)
}
pub fn inference(id: impl Into<String>) -> RuleBuilder {
RuleBuilder::new(id).kind(RuleKind::Inference)
}
pub fn constraint(id: impl Into<String>) -> RuleBuilder {
RuleBuilder::new(id).kind(RuleKind::Constraint)
}
pub fn matches(&self, triple: &Triple, bindings: &mut Bindings) -> bool {
self.conditions.iter().all(|c| c.matches(triple, bindings))
}
pub fn qualified_id(&self) -> String {
format!("{}:{}", self.kind.prefix(), self.id)
}
}
#[derive(Debug, Clone)]
pub struct RuleBuilder {
rule: Rule,
}
impl RuleBuilder {
pub fn new(id: impl Into<String>) -> Self {
Self {
rule: Rule::new(id, ""),
}
}
pub fn kind(mut self, kind: RuleKind) -> Self {
self.rule.kind = kind;
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.rule.name = name.into();
self
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.rule.description = desc.into();
self
}
pub fn when_predicate(mut self, predicate: impl Into<String>) -> Self {
self.rule
.conditions
.push(Condition::PredicateEquals(predicate.into()));
self
}
pub fn when_subject(mut self, pattern: Pattern) -> Self {
self.rule
.conditions
.push(Condition::SubjectMatches(pattern));
self
}
pub fn when_object(mut self, pattern: Pattern) -> Self {
self.rule.conditions.push(Condition::ObjectMatches(pattern));
self
}
pub fn when_exists(mut self, pattern: TriplePattern) -> Self {
self.rule.conditions.push(Condition::Exists(pattern));
self
}
pub fn when_not_exists(mut self, pattern: TriplePattern) -> Self {
self.rule.conditions.push(Condition::NotExists(pattern));
self
}
pub fn when<F>(mut self, check: F) -> Self
where
F: Fn(&Triple) -> bool + Send + Sync + 'static,
{
self.rule
.conditions
.push(Condition::Custom(Box::new(check)));
self
}
pub fn accept(mut self) -> Self {
self.rule.action = Action::Accept;
self
}
pub fn reject(mut self, reason: impl Into<String>) -> Self {
self.rule.action = Action::Reject(reason.into());
self
}
pub fn infer(mut self, pattern: TriplePattern) -> Self {
self.rule.action = Action::Infer(pattern);
self
}
pub fn warn(mut self, message: impl Into<String>) -> Self {
self.rule.action = Action::Warn(message.into());
self
}
pub fn priority(mut self, p: i32) -> Self {
self.rule.priority = p;
self
}
pub fn build(self) -> Rule {
self.rule
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RuleKind {
Integrity,
Authority,
Temporal,
Inference,
Constraint,
}
impl RuleKind {
pub fn prefix(&self) -> &'static str {
match self {
RuleKind::Integrity => "int",
RuleKind::Authority => "auth",
RuleKind::Temporal => "temp",
RuleKind::Inference => "inf",
RuleKind::Constraint => "con",
}
}
pub fn description(&self) -> &'static str {
match self {
RuleKind::Integrity => "Validates data integrity and consistency",
RuleKind::Authority => "Validates permissions and authority",
RuleKind::Temporal => "Validates temporal constraints",
RuleKind::Inference => "Derives new facts from existing data",
RuleKind::Constraint => "Enforces business logic constraints",
}
}
}
pub enum Condition {
PredicateEquals(String),
SubjectMatches(Pattern),
ObjectMatches(Pattern),
Exists(TriplePattern),
NotExists(TriplePattern),
Custom(Box<dyn Fn(&Triple) -> bool + Send + Sync>),
}
impl std::fmt::Debug for Condition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Condition::PredicateEquals(s) => f.debug_tuple("PredicateEquals").field(s).finish(),
Condition::SubjectMatches(p) => f.debug_tuple("SubjectMatches").field(p).finish(),
Condition::ObjectMatches(p) => f.debug_tuple("ObjectMatches").field(p).finish(),
Condition::Exists(p) => f.debug_tuple("Exists").field(p).finish(),
Condition::NotExists(p) => f.debug_tuple("NotExists").field(p).finish(),
Condition::Custom(_) => f.debug_tuple("Custom").field(&"<fn>").finish(),
}
}
}
impl Clone for Condition {
fn clone(&self) -> Self {
match self {
Condition::PredicateEquals(s) => Condition::PredicateEquals(s.clone()),
Condition::SubjectMatches(p) => Condition::SubjectMatches(p.clone()),
Condition::ObjectMatches(p) => Condition::ObjectMatches(p.clone()),
Condition::Exists(p) => Condition::Exists(p.clone()),
Condition::NotExists(p) => Condition::NotExists(p.clone()),
Condition::Custom(_) => Condition::Custom(Box::new(|_| true)),
}
}
}
impl Condition {
pub fn matches(&self, triple: &Triple, bindings: &mut Bindings) -> bool {
match self {
Condition::PredicateEquals(pred) => triple.predicate.as_str() == pred,
Condition::SubjectMatches(pattern) => pattern.matches_node(&triple.subject, bindings),
Condition::ObjectMatches(pattern) => pattern.matches_value(&triple.object, bindings),
Condition::Exists(_) => true,
Condition::NotExists(_) => true,
Condition::Custom(f) => f(triple),
}
}
}
impl Serialize for Condition {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(2))?;
match self {
Condition::PredicateEquals(p) => {
map.serialize_entry("type", "predicate_equals")?;
map.serialize_entry("value", p)?;
}
Condition::SubjectMatches(p) => {
map.serialize_entry("type", "subject_matches")?;
map.serialize_entry("pattern", p)?;
}
Condition::ObjectMatches(p) => {
map.serialize_entry("type", "object_matches")?;
map.serialize_entry("pattern", p)?;
}
Condition::Exists(p) => {
map.serialize_entry("type", "exists")?;
map.serialize_entry("pattern", p)?;
}
Condition::NotExists(p) => {
map.serialize_entry("type", "not_exists")?;
map.serialize_entry("pattern", p)?;
}
Condition::Custom(_) => {
map.serialize_entry("type", "custom")?;
map.serialize_entry("value", "<function>")?;
}
}
map.end()
}
}
impl<'de> Deserialize<'de> for Condition {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, MapAccess, Visitor};
struct ConditionVisitor;
impl<'de> Visitor<'de> for ConditionVisitor {
type Value = Condition;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a condition")
}
fn visit_map<M>(self, mut map: M) -> std::result::Result<Condition, M::Error>
where
M: MapAccess<'de>,
{
let mut cond_type: Option<String> = None;
let mut value: Option<String> = None;
let mut pattern: Option<Pattern> = None;
let mut triple_pattern: Option<TriplePattern> = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"type" => cond_type = Some(map.next_value()?),
"value" => value = Some(map.next_value()?),
"pattern" => {
let v: serde_json::Value = map.next_value()?;
if let Ok(p) = serde_json::from_value::<Pattern>(v.clone()) {
pattern = Some(p);
} else if let Ok(tp) = serde_json::from_value::<TriplePattern>(v) {
triple_pattern = Some(tp);
}
}
_ => {
let _: serde_json::Value = map.next_value()?;
}
}
}
let cond_type = cond_type.ok_or_else(|| de::Error::missing_field("type"))?;
match cond_type.as_str() {
"predicate_equals" => Ok(Condition::PredicateEquals(
value.ok_or_else(|| de::Error::missing_field("value"))?,
)),
"subject_matches" => Ok(Condition::SubjectMatches(
pattern.ok_or_else(|| de::Error::missing_field("pattern"))?,
)),
"object_matches" => Ok(Condition::ObjectMatches(
pattern.ok_or_else(|| de::Error::missing_field("pattern"))?,
)),
"exists" => Ok(Condition::Exists(
triple_pattern.ok_or_else(|| de::Error::missing_field("pattern"))?,
)),
"not_exists" => Ok(Condition::NotExists(
triple_pattern.ok_or_else(|| de::Error::missing_field("pattern"))?,
)),
_ => Err(de::Error::unknown_variant(
&cond_type,
&[
"predicate_equals",
"subject_matches",
"object_matches",
"exists",
"not_exists",
],
)),
}
}
}
deserializer.deserialize_map(ConditionVisitor)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Action {
Accept,
Reject(String),
Infer(TriplePattern),
Warn(String),
ChainTo(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Pattern {
Any,
Node(String),
Literal(String),
Variable(String),
Prefix(String),
Regex(String),
TypedLiteral { value: String, datatype: String },
}
impl Pattern {
pub fn matches_node(&self, node: &NodeId, bindings: &mut Bindings) -> bool {
let node_str = node_id_to_string(node);
match self {
Pattern::Any => true,
Pattern::Node(id) => &node_str == id,
Pattern::Variable(var) => {
if let Some(bound) = bindings.get(var) {
bound == &node_str
} else {
bindings.bind(var.clone(), node_str);
true
}
}
Pattern::Prefix(prefix) => node_str.starts_with(prefix),
Pattern::Regex(regex) => regex::Regex::new(regex)
.map(|re| re.is_match(&node_str))
.unwrap_or(false),
_ => false,
}
}
pub fn matches_value(&self, value: &Value, bindings: &mut Bindings) -> bool {
match (self, value) {
(Pattern::Any, _) => true,
(Pattern::Node(id), Value::Node(node)) => {
let node_str = node_id_to_string(node);
&node_str == id
}
(Pattern::Literal(lit), Value::String(val)) => val == lit,
(Pattern::Variable(var), val) => {
let val_str = value_to_string(val);
if let Some(bound) = bindings.get(var) {
bound == &val_str
} else {
bindings.bind(var.clone(), val_str);
true
}
}
(Pattern::Prefix(prefix), Value::String(val)) => val.starts_with(prefix),
(Pattern::Prefix(prefix), Value::Node(node)) => {
let node_str = node_id_to_string(node);
node_str.starts_with(prefix)
}
_ => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriplePattern {
pub subject: Pattern,
pub predicate: String,
pub object: Pattern,
}
impl TriplePattern {
pub fn new(subject: Pattern, predicate: impl Into<String>, object: Pattern) -> Self {
Self {
subject,
predicate: predicate.into(),
object,
}
}
pub fn matches(&self, triple: &Triple, bindings: &mut Bindings) -> bool {
triple.predicate.as_str() == self.predicate
&& self.subject.matches_node(&triple.subject, bindings)
&& self.object.matches_value(&triple.object, bindings)
}
pub fn instantiate(&self, bindings: &Bindings) -> Option<Triple> {
let subject = match &self.subject {
Pattern::Node(id) => NodeId::named(id),
Pattern::Variable(var) => NodeId::named(bindings.get(var)?),
_ => return None,
};
let predicate = Predicate::named(&self.predicate);
let object = match &self.object {
Pattern::Node(id) => Value::Node(NodeId::named(id)),
Pattern::Literal(lit) => Value::String(lit.clone()),
Pattern::Variable(var) => {
let val = bindings.get(var)?;
if val.contains(':') {
Value::Node(NodeId::named(val))
} else {
Value::String(val.clone())
}
}
_ => return None,
};
Some(Triple::new(subject, predicate, object))
}
}
#[derive(Debug, Clone, Default)]
pub struct Bindings {
values: HashMap<String, String>,
}
impl Bindings {
pub fn new() -> Self {
Self::default()
}
pub fn bind(&mut self, var: String, value: String) {
self.values.insert(var, value);
}
pub fn get(&self, var: &str) -> Option<&String> {
self.values.get(var)
}
pub fn is_bound(&self, var: &str) -> bool {
self.values.contains_key(var)
}
pub fn extend(&mut self, other: &Bindings) {
self.values.extend(other.values.clone());
}
pub fn clear(&mut self) {
self.values.clear();
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RuleSet {
pub name: String,
pub description: String,
pub rules: Vec<Rule>,
}
impl RuleSet {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
rules: Vec::new(),
}
}
pub fn add(&mut self, rule: Rule) {
self.rules.push(rule);
}
pub fn by_kind(&self, kind: RuleKind) -> Vec<&Rule> {
self.rules.iter().filter(|r| r.kind == kind).collect()
}
pub fn enabled_sorted(&self) -> Vec<&Rule> {
let mut rules: Vec<_> = self.rules.iter().filter(|r| r.enabled).collect();
rules.sort_by(|a, b| b.priority.cmp(&a.priority));
rules
}
pub fn set_enabled(&mut self, id: &str, enabled: bool) -> bool {
if let Some(rule) = self.rules.iter_mut().find(|r| r.id == id) {
rule.enabled = enabled;
true
} else {
false
}
}
pub fn get(&self, id: &str) -> Option<&Rule> {
self.rules.iter().find(|r| r.id == id)
}
pub fn len(&self) -> usize {
self.rules.len()
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
}
fn node_id_to_string(node: &NodeId) -> String {
match node {
NodeId::Named(s) => s.clone(),
NodeId::Hash(h) => format!("hash:{}", hex::encode(h)),
NodeId::Blank(id) => format!("_:b{}", id),
}
}
fn value_to_string(value: &Value) -> String {
match value {
Value::Node(node) => node_id_to_string(node),
Value::String(s) => s.clone(),
Value::Integer(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Boolean(b) => b.to_string(),
Value::DateTime(dt) => dt.clone(),
Value::Bytes(b) => format!("bytes:{}", hex::encode(b)),
Value::Typed { value, .. } => value.clone(),
Value::LangString { value, .. } => value.clone(),
Value::Json(v) => v.to_string(),
Value::Null => "null".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_builder() {
let rule = Rule::integrity("no_self_ref")
.name("No Self References")
.description("Prevents nodes from referencing themselves")
.when_predicate("references")
.reject("Self-references are not allowed")
.priority(100)
.build();
assert_eq!(rule.id, "no_self_ref");
assert_eq!(rule.kind, RuleKind::Integrity);
assert_eq!(rule.priority, 100);
}
#[test]
fn test_pattern_matching() {
let mut bindings = Bindings::new();
let pattern = Pattern::Variable("x".to_string());
let node = NodeId::named("user:alice");
assert!(pattern.matches_node(&node, &mut bindings));
assert_eq!(bindings.get("x"), Some(&"user:alice".to_string()));
let node2 = NodeId::named("user:alice");
assert!(pattern.matches_node(&node2, &mut bindings));
let node3 = NodeId::named("user:bob");
assert!(!pattern.matches_node(&node3, &mut bindings));
}
#[test]
fn test_triple_pattern() {
let pattern = TriplePattern::new(
Pattern::Variable("s".to_string()),
"knows",
Pattern::Variable("o".to_string()),
);
let triple = Triple::new(
NodeId::named("alice"),
Predicate::named("knows"),
Value::Node(NodeId::named("bob")),
);
let mut bindings = Bindings::new();
assert!(pattern.matches(&triple, &mut bindings));
assert_eq!(bindings.get("s"), Some(&"alice".to_string()));
assert_eq!(bindings.get("o"), Some(&"bob".to_string()));
}
#[test]
fn test_ruleset() {
let mut ruleset = RuleSet::new("test");
ruleset.add(Rule::integrity("r1").priority(10).build());
ruleset.add(Rule::authority("r2").priority(20).build());
ruleset.add(Rule::inference("r3").priority(5).build());
assert_eq!(ruleset.len(), 3);
let sorted = ruleset.enabled_sorted();
assert_eq!(sorted[0].id, "r2"); assert_eq!(sorted[1].id, "r1");
assert_eq!(sorted[2].id, "r3");
}
#[test]
fn test_rule_kind() {
assert_eq!(RuleKind::Integrity.prefix(), "int");
assert_eq!(RuleKind::Authority.prefix(), "auth");
assert_eq!(RuleKind::Inference.prefix(), "inf");
}
}