drasi_source_mapping/
config.rs1use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24pub struct SourceMapping {
25 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub when: Option<MappingCondition>,
28
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub operation: Option<OperationType>,
32
33 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub operation_from: Option<String>,
36
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub operation_map: Option<HashMap<String, OperationType>>,
40
41 pub element_type: ElementType,
43
44 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub effective_from: Option<EffectiveFromConfig>,
47
48 pub template: ElementTemplate,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub struct MappingCondition {
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub header: Option<String>,
58
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub field: Option<String>,
62
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub equals: Option<String>,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub contains: Option<String>,
70
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub regex: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
78#[serde(rename_all = "lowercase")]
79pub enum OperationType {
80 Insert,
81 Update,
82 Delete,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87#[serde(rename_all = "lowercase")]
88pub enum ElementType {
89 Node,
90 Relation,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95#[serde(untagged)]
96pub enum EffectiveFromConfig {
97 Simple(String),
99 Explicit {
101 value: String,
103 format: TimestampFormat,
105 },
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
110#[serde(rename_all = "snake_case")]
111pub enum TimestampFormat {
112 Iso8601,
114 UnixSeconds,
116 UnixMillis,
118 UnixNanos,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124pub struct ElementTemplate {
125 pub id: String,
127
128 pub labels: Vec<String>,
130
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub properties: Option<serde_json::Value>,
134
135 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub from: Option<String>,
138
139 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub to: Option<String>,
142}
143
144impl SourceMapping {
145 pub fn validate(&self) -> anyhow::Result<()> {
147 if self.operation.is_none() && self.operation_from.is_none() {
149 return Err(anyhow::anyhow!(
150 "either 'operation' or 'operation_from' must be specified"
151 ));
152 }
153
154 if self.operation_from.is_some() && self.operation_map.is_none() {
156 return Err(anyhow::anyhow!(
157 "'operation_map' is required when using 'operation_from'"
158 ));
159 }
160
161 self.template.validate(&self.element_type)?;
163
164 Ok(())
165 }
166}
167
168impl ElementTemplate {
169 pub fn validate(&self, element_type: &ElementType) -> anyhow::Result<()> {
171 if self.id.is_empty() {
172 return Err(anyhow::anyhow!("template.id cannot be empty"));
173 }
174
175 if self.labels.is_empty() {
176 return Err(anyhow::anyhow!("template.labels cannot be empty"));
177 }
178
179 if *element_type == ElementType::Relation {
181 if self.from.is_none() {
182 return Err(anyhow::anyhow!(
183 "template.from is required for relation elements"
184 ));
185 }
186 if self.to.is_none() {
187 return Err(anyhow::anyhow!(
188 "template.to is required for relation elements"
189 ));
190 }
191 }
192
193 Ok(())
194 }
195}