use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConstraintKind {
Unique,
ForeignKey {
ref_collection: String,
ref_key: String,
},
BiTemporalFK {
ref_collection: String,
ref_key: String,
},
NotNull,
Check {
description: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Constraint {
pub name: String,
pub collection: String,
pub field: String,
pub kind: ConstraintKind,
}
#[derive(Debug, Clone, Default)]
pub struct ConstraintSet {
constraints: Vec<Constraint>,
}
impl ConstraintSet {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, constraint: Constraint) {
self.constraints.push(constraint);
}
pub fn add_unique(&mut self, name: &str, collection: &str, field: &str) {
self.add(Constraint {
name: name.to_string(),
collection: collection.to_string(),
field: field.to_string(),
kind: ConstraintKind::Unique,
});
}
pub fn add_foreign_key(
&mut self,
name: &str,
collection: &str,
field: &str,
ref_collection: &str,
ref_key: &str,
) {
self.add(Constraint {
name: name.to_string(),
collection: collection.to_string(),
field: field.to_string(),
kind: ConstraintKind::ForeignKey {
ref_collection: ref_collection.to_string(),
ref_key: ref_key.to_string(),
},
});
}
pub fn add_bitemporal_fk(
&mut self,
name: &str,
collection: &str,
field: &str,
ref_collection: &str,
ref_key: &str,
) {
self.add(Constraint {
name: name.to_string(),
collection: collection.to_string(),
field: field.to_string(),
kind: ConstraintKind::BiTemporalFK {
ref_collection: ref_collection.to_string(),
ref_key: ref_key.to_string(),
},
});
}
pub fn add_not_null(&mut self, name: &str, collection: &str, field: &str) {
self.add(Constraint {
name: name.to_string(),
collection: collection.to_string(),
field: field.to_string(),
kind: ConstraintKind::NotNull,
});
}
pub fn for_collection(&self, collection: &str) -> Vec<&Constraint> {
self.constraints
.iter()
.filter(|c| c.collection == collection)
.collect()
}
pub fn all(&self) -> &[Constraint] {
&self.constraints
}
pub fn len(&self) -> usize {
self.constraints.len()
}
pub fn is_empty(&self) -> bool {
self.constraints.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constraint_set_operations() {
let mut cs = ConstraintSet::new();
cs.add_unique("users_email_unique", "users", "email");
cs.add_not_null("users_name_nn", "users", "name");
cs.add_foreign_key("posts_author_fk", "posts", "author_id", "users", "id");
cs.add_bitemporal_fk("orders_user_btfk", "orders", "user_id", "users", "id");
assert_eq!(cs.len(), 4);
assert_eq!(cs.for_collection("users").len(), 2);
assert_eq!(cs.for_collection("posts").len(), 1);
assert_eq!(cs.for_collection("orders").len(), 1);
assert_eq!(cs.for_collection("missing").len(), 0);
let btfk = cs.for_collection("orders")[0];
assert!(matches!(btfk.kind, ConstraintKind::BiTemporalFK { .. }));
}
}