use linkrs_core::{DataType, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SchemaObjectType {
Vertex,
Edge,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeDetails {
PropertyAdded {
name: String,
data_type: DataType,
nullable: bool,
default_value: Option<Value>,
},
PropertyRemoved { name: String, data_type: DataType },
PropertyRenamed { old_name: String, new_name: String },
PropertyTypeModified {
name: String,
old_type: DataType,
new_type: DataType,
},
PropertyNullabilityChanged {
name: String,
was_nullable: bool,
now_nullable: bool,
},
PropertyDefaultValueChanged {
name: String,
old_default: Option<Value>,
new_default: Option<Value>,
},
PrimaryKeyChanged {
old_property: String,
new_property: String,
},
}
impl ChangeDetails {
pub fn is_breaking(&self) -> bool {
matches!(
self,
ChangeDetails::PropertyRemoved { .. }
| ChangeDetails::PropertyTypeModified { .. }
| ChangeDetails::PrimaryKeyChanged { .. }
)
}
pub fn description(&self) -> String {
match self {
ChangeDetails::PropertyAdded { name, .. } => {
format!("Added property '{}'", name)
}
ChangeDetails::PropertyRemoved { name, .. } => {
format!("Removed property '{}'", name)
}
ChangeDetails::PropertyRenamed { old_name, new_name } => {
format!("Renamed property '{}' to '{}'", old_name, new_name)
}
ChangeDetails::PropertyTypeModified {
name,
old_type,
new_type,
} => {
format!(
"Modified property '{}' type from {:?} to {:?}",
name, old_type, new_type
)
}
ChangeDetails::PropertyNullabilityChanged {
name,
was_nullable,
now_nullable,
} => {
format!(
"Changed property '{}' nullability from {} to {}",
name, was_nullable, now_nullable
)
}
ChangeDetails::PropertyDefaultValueChanged {
name,
old_default,
new_default,
} => {
format!(
"Changed property '{}' default value from {:?} to {:?}",
name, old_default, new_default
)
}
ChangeDetails::PrimaryKeyChanged {
old_property,
new_property,
} => {
format!(
"Changed primary key from '{}' to '{}'",
old_property, new_property
)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyChange {
pub version: u64,
pub timestamp_ms: u64,
pub details: ChangeDetails,
}
impl PropertyChange {
pub fn new(
version: u64,
_object_type: SchemaObjectType,
_label_id: u32,
_label_name: String,
details: ChangeDetails,
) -> Self {
let timestamp_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
Self {
version,
timestamp_ms,
details,
}
}
pub fn is_breaking(&self) -> bool {
self.details.is_breaking()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangeLog {
pub object_type: SchemaObjectType,
pub label_id: u32,
pub label_name: String,
pub changes: HashMap<u64, Vec<PropertyChange>>,
}
impl ChangeLog {
pub fn new(object_type: SchemaObjectType, label_id: u32, label_name: String) -> Self {
Self {
object_type,
label_id,
label_name,
changes: HashMap::new(),
}
}
pub fn add_change(&mut self, change: PropertyChange) {
self.changes.entry(change.version).or_default().push(change);
}
pub fn get_version_changes(&self, version: u64) -> Option<&Vec<PropertyChange>> {
self.changes.get(&version)
}
pub fn get_versions(&self) -> Vec<u64> {
let mut versions: Vec<_> = self.changes.keys().copied().collect();
versions.sort_unstable();
versions
}
pub fn latest_version(&self) -> Option<u64> {
self.changes.keys().max().copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_property_change_creation() {
let change = PropertyChange::new(
1,
SchemaObjectType::Vertex,
1,
"User".to_string(),
ChangeDetails::PropertyAdded {
name: "email".to_string(),
data_type: DataType::String,
nullable: false,
default_value: None,
},
);
assert_eq!(change.version, 1);
assert!(!change.is_breaking());
}
#[test]
fn test_breaking_changes() {
let breaking_change = PropertyChange::new(
1,
SchemaObjectType::Vertex,
1,
"User".to_string(),
ChangeDetails::PropertyRemoved {
name: "old_field".to_string(),
data_type: DataType::String,
},
);
assert!(breaking_change.is_breaking());
}
#[test]
fn test_changelog_operations() {
let mut log = ChangeLog::new(SchemaObjectType::Vertex, 1, "User".to_string());
let change1 = PropertyChange::new(
1,
SchemaObjectType::Vertex,
1,
"User".to_string(),
ChangeDetails::PropertyAdded {
name: "email".to_string(),
data_type: DataType::String,
nullable: false,
default_value: None,
},
);
log.add_change(change1);
assert_eq!(log.get_versions(), vec![1]);
assert_eq!(log.latest_version(), Some(1));
assert!(log.get_version_changes(1).is_some());
}
}