use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SourceMapping {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub when: Option<MappingCondition>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation: Option<OperationType>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation_from: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation_map: Option<HashMap<String, OperationType>>,
pub element_type: ElementType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effective_from: Option<EffectiveFromConfig>,
pub template: ElementTemplate,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MappingCondition {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub header: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub equals: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contains: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub regex: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OperationType {
Insert,
Update,
Delete,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ElementType {
Node,
Relation,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum EffectiveFromConfig {
Simple(String),
Explicit {
value: String,
format: TimestampFormat,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TimestampFormat {
Iso8601,
UnixSeconds,
UnixMillis,
UnixNanos,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ElementTemplate {
pub id: String,
pub labels: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub properties: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
}
impl SourceMapping {
pub fn validate(&self) -> anyhow::Result<()> {
if self.operation.is_none() && self.operation_from.is_none() {
return Err(anyhow::anyhow!(
"either 'operation' or 'operation_from' must be specified"
));
}
if self.operation_from.is_some() && self.operation_map.is_none() {
return Err(anyhow::anyhow!(
"'operation_map' is required when using 'operation_from'"
));
}
self.template.validate(&self.element_type)?;
Ok(())
}
}
impl ElementTemplate {
pub fn validate(&self, element_type: &ElementType) -> anyhow::Result<()> {
if self.id.is_empty() {
return Err(anyhow::anyhow!("template.id cannot be empty"));
}
if self.labels.is_empty() {
return Err(anyhow::anyhow!("template.labels cannot be empty"));
}
if *element_type == ElementType::Relation {
if self.from.is_none() {
return Err(anyhow::anyhow!(
"template.from is required for relation elements"
));
}
if self.to.is_none() {
return Err(anyhow::anyhow!(
"template.to is required for relation elements"
));
}
}
Ok(())
}
}