use chrono::{DateTime, FixedOffset};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum BootstrapScriptRecord {
Comment(CommentRecord),
Header(BootstrapHeaderRecord),
Label(LabelRecord),
Node(NodeRecord),
Relation(RelationRecord),
Finish(BootstrapFinishRecord),
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct BootstrapHeaderRecord {
pub start_time: DateTime<FixedOffset>,
#[serde(default)]
pub description: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NodeRecord {
pub id: String,
pub labels: Vec<String>,
#[serde(default)]
pub properties: serde_json::Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RelationRecord {
pub id: String,
pub labels: Vec<String>,
pub start_id: String,
pub start_label: Option<String>,
pub end_id: String,
pub end_label: Option<String>,
#[serde(default)]
pub properties: serde_json::Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LabelRecord {
#[serde(default)]
pub offset_ns: u64,
pub label: String,
#[serde(default)]
pub description: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommentRecord {
pub comment: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BootstrapFinishRecord {
#[serde(default)]
pub description: String,
}
#[derive(Clone, Debug, Serialize)]
pub struct SequencedBootstrapScriptRecord {
pub seq: u64,
pub record: BootstrapScriptRecord,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_header_record_serialization() {
let header = BootstrapHeaderRecord {
start_time: DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z").unwrap(),
description: "Test Script".to_string(),
};
let record = BootstrapScriptRecord::Header(header);
let json = serde_json::to_string(&record).unwrap();
assert!(json.contains(r#""kind":"Header"#));
assert!(json.contains(r#""description":"Test Script"#));
let deserialized: BootstrapScriptRecord = serde_json::from_str(&json).unwrap();
if let BootstrapScriptRecord::Header(h) = deserialized {
assert_eq!(h.description, "Test Script");
} else {
panic!("Expected Header record");
}
}
#[test]
fn test_node_record_serialization() {
let node = NodeRecord {
id: "n1".to_string(),
labels: vec!["Person".to_string()],
properties: json!({"name": "Alice", "age": 30}),
};
let record = BootstrapScriptRecord::Node(node);
let json = serde_json::to_string(&record).unwrap();
assert!(json.contains(r#""kind":"Node"#));
assert!(json.contains(r#""id":"n1"#));
let deserialized: BootstrapScriptRecord = serde_json::from_str(&json).unwrap();
if let BootstrapScriptRecord::Node(n) = deserialized {
assert_eq!(n.id, "n1");
assert_eq!(n.labels, vec!["Person"]);
} else {
panic!("Expected Node record");
}
}
#[test]
fn test_relation_record_serialization() {
let relation = RelationRecord {
id: "r1".to_string(),
labels: vec!["KNOWS".to_string()],
start_id: "n1".to_string(),
start_label: Some("Person".to_string()),
end_id: "n2".to_string(),
end_label: Some("Person".to_string()),
properties: json!({"since": 2020}),
};
let record = BootstrapScriptRecord::Relation(relation);
let json = serde_json::to_string(&record).unwrap();
assert!(json.contains(r#""kind":"Relation"#));
assert!(json.contains(r#""start_id":"n1"#));
let deserialized: BootstrapScriptRecord = serde_json::from_str(&json).unwrap();
if let BootstrapScriptRecord::Relation(r) = deserialized {
assert_eq!(r.id, "r1");
assert_eq!(r.start_id, "n1");
assert_eq!(r.end_id, "n2");
} else {
panic!("Expected Relation record");
}
}
#[test]
fn test_comment_record_serialization() {
let comment = CommentRecord {
comment: "This is a comment".to_string(),
};
let record = BootstrapScriptRecord::Comment(comment);
let json = serde_json::to_string(&record).unwrap();
assert!(json.contains(r#""kind":"Comment"#));
let deserialized: BootstrapScriptRecord = serde_json::from_str(&json).unwrap();
if let BootstrapScriptRecord::Comment(c) = deserialized {
assert_eq!(c.comment, "This is a comment");
} else {
panic!("Expected Comment record");
}
}
#[test]
fn test_label_record_serialization() {
let label = LabelRecord {
offset_ns: 1000,
label: "checkpoint_1".to_string(),
description: "First checkpoint".to_string(),
};
let record = BootstrapScriptRecord::Label(label);
let json = serde_json::to_string(&record).unwrap();
assert!(json.contains(r#""kind":"Label"#));
let deserialized: BootstrapScriptRecord = serde_json::from_str(&json).unwrap();
if let BootstrapScriptRecord::Label(l) = deserialized {
assert_eq!(l.label, "checkpoint_1");
assert_eq!(l.offset_ns, 1000);
} else {
panic!("Expected Label record");
}
}
#[test]
fn test_finish_record_serialization() {
let finish = BootstrapFinishRecord {
description: "End of script".to_string(),
};
let record = BootstrapScriptRecord::Finish(finish);
let json = serde_json::to_string(&record).unwrap();
assert!(json.contains(r#""kind":"Finish"#));
let deserialized: BootstrapScriptRecord = serde_json::from_str(&json).unwrap();
if let BootstrapScriptRecord::Finish(f) = deserialized {
assert_eq!(f.description, "End of script");
} else {
panic!("Expected Finish record");
}
}
#[test]
fn test_default_header_record() {
let header = BootstrapHeaderRecord::default();
assert!(header.description.is_empty());
}
#[test]
fn test_node_with_empty_properties() {
let json = r#"{"kind":"Node","id":"n1","labels":["Test"]}"#;
let record: BootstrapScriptRecord = serde_json::from_str(json).unwrap();
if let BootstrapScriptRecord::Node(n) = record {
assert_eq!(n.id, "n1");
assert_eq!(n.properties, serde_json::Value::Null);
} else {
panic!("Expected Node record");
}
}
#[test]
fn test_relation_without_optional_labels() {
let json =
r#"{"kind":"Relation","id":"r1","labels":["REL"],"start_id":"n1","end_id":"n2"}"#;
let record: BootstrapScriptRecord = serde_json::from_str(json).unwrap();
if let BootstrapScriptRecord::Relation(r) = record {
assert_eq!(r.start_label, None);
assert_eq!(r.end_label, None);
} else {
panic!("Expected Relation record");
}
}
}