use std::collections::HashMap;
pub mod builder;
pub mod error;
pub mod serialize;
pub mod types;
pub mod validation;
pub use builder::{EdgeSchemaBuilder, SchemaBuilder, VertexSchemaBuilder};
pub use error::{SchemaError, SchemaResult};
pub use serialize::{
deserialize_schema, serialize_schema, SchemaSerializeError, SCHEMA_FORMAT_VERSION,
SCHEMA_HEADER_SIZE, SCHEMA_MAGIC,
};
pub use types::{EdgeSchema, PropertyDef, PropertyType, VertexSchema};
pub use validation::{
apply_defaults, validate_edge, validate_property_update, validate_vertex, ValidationResult,
};
#[derive(Clone, Debug, Default)]
pub struct GraphSchema {
pub vertex_schemas: HashMap<String, VertexSchema>,
pub edge_schemas: HashMap<String, EdgeSchema>,
pub mode: ValidationMode,
}
impl GraphSchema {
pub fn new() -> Self {
Self::default()
}
pub fn with_mode(mode: ValidationMode) -> Self {
Self {
mode,
..Default::default()
}
}
pub fn vertex_labels(&self) -> impl Iterator<Item = &str> {
self.vertex_schemas.keys().map(|s| s.as_str())
}
pub fn edge_labels(&self) -> impl Iterator<Item = &str> {
self.edge_schemas.keys().map(|s| s.as_str())
}
pub fn vertex_schema(&self, label: &str) -> Option<&VertexSchema> {
self.vertex_schemas.get(label)
}
pub fn edge_schema(&self, label: &str) -> Option<&EdgeSchema> {
self.edge_schemas.get(label)
}
pub fn has_vertex_schema(&self, label: &str) -> bool {
self.vertex_schemas.contains_key(label)
}
pub fn has_edge_schema(&self, label: &str) -> bool {
self.edge_schemas.contains_key(label)
}
pub fn edges_from(&self, vertex_label: &str) -> Vec<&str> {
self.edge_schemas
.iter()
.filter(|(_, schema)| {
schema.from_labels.is_empty()
|| schema.from_labels.iter().any(|l| l == vertex_label)
})
.map(|(label, _)| label.as_str())
.collect()
}
pub fn edges_to(&self, vertex_label: &str) -> Vec<&str> {
self.edge_schemas
.iter()
.filter(|(_, schema)| {
schema.to_labels.is_empty() || schema.to_labels.iter().any(|l| l == vertex_label)
})
.map(|(label, _)| label.as_str())
.collect()
}
pub fn is_empty(&self) -> bool {
self.vertex_schemas.is_empty() && self.edge_schemas.is_empty()
}
pub fn type_count(&self) -> usize {
self.vertex_schemas.len() + self.edge_schemas.len()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ValidationMode {
#[default]
None,
Warn,
Strict,
Closed,
}
impl std::fmt::Display for ValidationMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationMode::None => write!(f, "NONE"),
ValidationMode::Warn => write!(f, "WARN"),
ValidationMode::Strict => write!(f, "STRICT"),
ValidationMode::Closed => write!(f, "CLOSED"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn graph_schema_new() {
let schema = GraphSchema::new();
assert_eq!(schema.mode, ValidationMode::None);
assert!(schema.is_empty());
assert_eq!(schema.type_count(), 0);
}
#[test]
fn graph_schema_with_mode() {
let schema = GraphSchema::with_mode(ValidationMode::Closed);
assert_eq!(schema.mode, ValidationMode::Closed);
}
#[test]
fn graph_schema_vertex_labels() {
let schema = SchemaBuilder::new()
.vertex("Person")
.done()
.vertex("Company")
.done()
.build();
let labels: Vec<_> = schema.vertex_labels().collect();
assert_eq!(labels.len(), 2);
assert!(labels.contains(&"Person"));
assert!(labels.contains(&"Company"));
}
#[test]
fn graph_schema_edge_labels() {
let schema = SchemaBuilder::new()
.vertex("Person")
.done()
.edge("KNOWS")
.from(&["Person"])
.to(&["Person"])
.done()
.edge("FOLLOWS")
.from(&["Person"])
.to(&["Person"])
.done()
.build();
let labels: Vec<_> = schema.edge_labels().collect();
assert_eq!(labels.len(), 2);
assert!(labels.contains(&"KNOWS"));
assert!(labels.contains(&"FOLLOWS"));
}
#[test]
fn graph_schema_edges_from_to() {
let schema = SchemaBuilder::new()
.vertex("Person")
.done()
.vertex("Company")
.done()
.edge("WORKS_AT")
.from(&["Person"])
.to(&["Company"])
.done()
.edge("KNOWS")
.from(&["Person"])
.to(&["Person"])
.done()
.edge("OWNS")
.from(&["Person", "Company"])
.to(&["Company"])
.done()
.build();
let from_person = schema.edges_from("Person");
assert!(from_person.contains(&"WORKS_AT"));
assert!(from_person.contains(&"KNOWS"));
assert!(from_person.contains(&"OWNS"));
let from_company = schema.edges_from("Company");
assert!(from_company.contains(&"OWNS"));
assert!(!from_company.contains(&"WORKS_AT"));
let to_company = schema.edges_to("Company");
assert!(to_company.contains(&"WORKS_AT"));
assert!(to_company.contains(&"OWNS"));
assert!(!to_company.contains(&"KNOWS"));
}
#[test]
fn graph_schema_type_count() {
let schema = SchemaBuilder::new()
.vertex("Person")
.done()
.vertex("Company")
.done()
.edge("WORKS_AT")
.from(&["Person"])
.to(&["Company"])
.done()
.build();
assert!(!schema.is_empty());
assert_eq!(schema.type_count(), 3);
assert_eq!(schema.vertex_schemas.len(), 2);
assert_eq!(schema.edge_schemas.len(), 1);
}
#[test]
fn validation_mode_display() {
assert_eq!(format!("{}", ValidationMode::None), "NONE");
assert_eq!(format!("{}", ValidationMode::Warn), "WARN");
assert_eq!(format!("{}", ValidationMode::Strict), "STRICT");
assert_eq!(format!("{}", ValidationMode::Closed), "CLOSED");
}
#[test]
fn validation_mode_default() {
let mode: ValidationMode = Default::default();
assert_eq!(mode, ValidationMode::None);
}
#[test]
fn validation_mode_equality() {
assert_eq!(ValidationMode::Strict, ValidationMode::Strict);
assert_ne!(ValidationMode::Strict, ValidationMode::Closed);
}
}