use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstraintType {
Unique,
Key,
KeyRef,
}
#[derive(Debug, Clone)]
pub struct IdentityConstraint {
pub name: String,
pub constraint_type: ConstraintType,
pub selector: String,
pub fields: Vec<String>,
pub refer: Option<String>,
}
impl IdentityConstraint {
pub fn unique(name: impl Into<String>, selector: impl Into<String>) -> Self {
Self {
name: name.into(),
constraint_type: ConstraintType::Unique,
selector: selector.into(),
fields: Vec::new(),
refer: None,
}
}
pub fn key(name: impl Into<String>, selector: impl Into<String>) -> Self {
Self {
name: name.into(),
constraint_type: ConstraintType::Key,
selector: selector.into(),
fields: Vec::new(),
refer: None,
}
}
pub fn keyref(
name: impl Into<String>,
selector: impl Into<String>,
refer: impl Into<String>,
) -> Self {
Self {
name: name.into(),
constraint_type: ConstraintType::KeyRef,
selector: selector.into(),
fields: Vec::new(),
refer: Some(refer.into()),
}
}
pub fn with_field(mut self, field: impl Into<String>) -> Self {
self.fields.push(field.into());
self
}
pub fn with_fields(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.fields.extend(fields.into_iter().map(Into::into));
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyValue {
pub values: Vec<String>,
}
impl KeyValue {
pub fn new(values: Vec<String>) -> Self {
Self { values }
}
pub fn single(value: impl Into<String>) -> Self {
Self {
values: vec![value.into()],
}
}
pub fn has_null(&self) -> bool {
self.values.iter().any(|v| v.is_empty())
}
pub fn is_complete(&self, expected_fields: usize) -> bool {
self.values.len() == expected_fields && !self.has_null()
}
}
#[derive(Debug, Clone)]
pub enum ConstraintError {
DuplicateKey {
constraint: String,
value: KeyValue,
},
NullKeyValue {
constraint: String,
field_index: usize,
},
KeyRefNotFound {
constraint: String,
refer: String,
value: KeyValue,
},
SelectorError {
constraint: String,
message: String,
},
FieldError {
constraint: String,
field_index: usize,
message: String,
},
}
impl std::fmt::Display for ConstraintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConstraintError::DuplicateKey { constraint, value } => {
write!(
f,
"duplicate value {:?} in constraint '{}'",
value.values, constraint
)
}
ConstraintError::NullKeyValue {
constraint,
field_index,
} => {
write!(
f,
"null value in key field {} of constraint '{}'",
field_index, constraint
)
}
ConstraintError::KeyRefNotFound {
constraint,
refer,
value,
} => {
write!(
f,
"keyref '{}' value {:?} not found in key '{}'",
constraint, value.values, refer
)
}
ConstraintError::SelectorError {
constraint,
message,
} => {
write!(
f,
"selector error in constraint '{}': {}",
constraint, message
)
}
ConstraintError::FieldError {
constraint,
field_index,
message,
} => {
write!(
f,
"field {} error in constraint '{}': {}",
field_index, constraint, message
)
}
}
}
}
impl std::error::Error for ConstraintError {}
#[derive(Debug, Clone, Default)]
pub struct KeyValueSet {
values: HashSet<KeyValue>,
constraint_name: String,
#[allow(dead_code)]
field_count: usize,
}
impl KeyValueSet {
pub fn new(constraint_name: impl Into<String>, field_count: usize) -> Self {
Self {
values: HashSet::new(),
constraint_name: constraint_name.into(),
field_count,
}
}
pub fn add(&mut self, value: KeyValue) -> Result<(), ConstraintError> {
if !self.values.insert(value.clone()) {
return Err(ConstraintError::DuplicateKey {
constraint: self.constraint_name.clone(),
value,
});
}
Ok(())
}
pub fn contains(&self, value: &KeyValue) -> bool {
self.values.contains(value)
}
pub fn values(&self) -> impl Iterator<Item = &KeyValue> {
self.values.iter()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
#[derive(Debug, Default)]
pub struct ConstraintValidator {
key_values: HashMap<String, KeyValueSet>,
pending_keyrefs: Vec<PendingKeyRef>,
}
#[derive(Debug)]
struct PendingKeyRef {
constraint_name: String,
refer: String,
value: KeyValue,
}
impl ConstraintValidator {
pub fn new() -> Self {
Self::default()
}
pub fn register_key(&mut self, name: &str, field_count: usize) {
self.key_values
.insert(name.to_string(), KeyValueSet::new(name, field_count));
}
pub fn add_key_value(
&mut self,
constraint: &IdentityConstraint,
value: KeyValue,
) -> Result<(), ConstraintError> {
if constraint.constraint_type == ConstraintType::Key && value.has_null() {
for (idx, v) in value.values.iter().enumerate() {
if v.is_empty() {
return Err(ConstraintError::NullKeyValue {
constraint: constraint.name.clone(),
field_index: idx,
});
}
}
}
let set = self
.key_values
.entry(constraint.name.clone())
.or_insert_with(|| KeyValueSet::new(&constraint.name, constraint.fields.len()));
if constraint.constraint_type == ConstraintType::Unique && value.has_null() {
return Ok(());
}
set.add(value)
}
pub fn add_keyref_value(&mut self, constraint: &IdentityConstraint, value: KeyValue) {
if let Some(refer) = &constraint.refer {
if value.has_null() {
return;
}
self.pending_keyrefs.push(PendingKeyRef {
constraint_name: constraint.name.clone(),
refer: refer.clone(),
value,
});
}
}
pub fn validate_keyrefs(&self) -> Result<(), Vec<ConstraintError>> {
let mut errors = Vec::new();
for keyref in &self.pending_keyrefs {
if let Some(key_set) = self.key_values.get(&keyref.refer) {
if !key_set.contains(&keyref.value) {
errors.push(ConstraintError::KeyRefNotFound {
constraint: keyref.constraint_name.clone(),
refer: keyref.refer.clone(),
value: keyref.value.clone(),
});
}
} else {
errors.push(ConstraintError::KeyRefNotFound {
constraint: keyref.constraint_name.clone(),
refer: keyref.refer.clone(),
value: keyref.value.clone(),
});
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn reset(&mut self) {
self.key_values.clear();
self.pending_keyrefs.clear();
}
pub fn get_key_values(&self, name: &str) -> Option<&KeyValueSet> {
self.key_values.get(name)
}
}
#[derive(Debug, Clone)]
pub struct CompiledConstraint {
pub name: String,
pub constraint_type: ConstraintType,
pub selector_xpath: String,
pub field_xpaths: Vec<String>,
pub refer: Option<String>,
}
impl CompiledConstraint {
pub fn new(constraint: &IdentityConstraint) -> Self {
Self {
name: constraint.name.clone(),
constraint_type: constraint.constraint_type,
selector_xpath: constraint.selector.clone(),
field_xpaths: constraint.fields.clone(),
refer: constraint.refer.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unique_constraint() {
let constraint = IdentityConstraint::unique("id-unique", ".//item").with_field("@id");
let mut validator = ConstraintValidator::new();
assert!(
validator
.add_key_value(&constraint, KeyValue::single("1"))
.is_ok()
);
assert!(
validator
.add_key_value(&constraint, KeyValue::single("2"))
.is_ok()
);
let result = validator.add_key_value(&constraint, KeyValue::single("1"));
assert!(matches!(result, Err(ConstraintError::DuplicateKey { .. })));
}
#[test]
fn test_key_constraint_no_nulls() {
let constraint = IdentityConstraint::key("item-key", ".//item").with_field("@id");
let mut validator = ConstraintValidator::new();
let result = validator.add_key_value(&constraint, KeyValue::single(""));
assert!(matches!(result, Err(ConstraintError::NullKeyValue { .. })));
}
#[test]
fn test_unique_constraint_allows_nulls() {
let constraint = IdentityConstraint::unique("id-unique", ".//item").with_field("@id");
let mut validator = ConstraintValidator::new();
assert!(
validator
.add_key_value(&constraint, KeyValue::single(""))
.is_ok()
);
assert!(
validator
.add_key_value(&constraint, KeyValue::single(""))
.is_ok()
);
}
#[test]
fn test_keyref_validation() {
let key = IdentityConstraint::key("category-key", ".//category").with_field("@id");
let keyref = IdentityConstraint::keyref("item-category", ".//item", "category-key")
.with_field("@category");
let mut validator = ConstraintValidator::new();
validator
.add_key_value(&key, KeyValue::single("cat1"))
.unwrap();
validator
.add_key_value(&key, KeyValue::single("cat2"))
.unwrap();
validator.add_keyref_value(&keyref, KeyValue::single("cat1"));
validator.add_keyref_value(&keyref, KeyValue::single("cat3"));
let result = validator.validate_keyrefs();
assert!(result.is_err());
let errors = result.unwrap_err();
assert_eq!(errors.len(), 1);
}
#[test]
fn test_composite_key() {
let constraint =
IdentityConstraint::key("composite-key", ".//item").with_fields(["@type", "@id"]);
let mut validator = ConstraintValidator::new();
assert!(
validator
.add_key_value(&constraint, KeyValue::new(vec!["A".into(), "1".into()]))
.is_ok()
);
assert!(
validator
.add_key_value(&constraint, KeyValue::new(vec!["A".into(), "2".into()]))
.is_ok()
);
assert!(
validator
.add_key_value(&constraint, KeyValue::new(vec!["B".into(), "1".into()]))
.is_ok()
);
let result =
validator.add_key_value(&constraint, KeyValue::new(vec!["A".into(), "1".into()]));
assert!(matches!(result, Err(ConstraintError::DuplicateKey { .. })));
}
#[test]
fn test_key_value_set() {
let mut set = KeyValueSet::new("test", 1);
assert!(set.add(KeyValue::single("a")).is_ok());
assert!(set.add(KeyValue::single("b")).is_ok());
assert!(set.contains(&KeyValue::single("a")));
assert!(!set.contains(&KeyValue::single("c")));
assert_eq!(set.len(), 2);
}
}