1use crate::{
2 ast::{Command, CommandType, NestedAttrType, NestedValue, OCAAst, ObjectKind},
3 errors::Error,
4};
5use indexmap::{indexmap, IndexMap};
6use log::debug;
7
8type CaptureAttributes = IndexMap<String, NestedAttrType>;
9
10pub trait Validator {
19 fn validate(&self, ast: &OCAAst, command: Command) -> Result<bool, Error>;
20}
21
22pub struct OCAValidator {}
23
24impl Validator for OCAValidator {
25 fn validate(&self, ast: &OCAAst, command: Command) -> Result<bool, Error> {
26 let mut errors = Vec::new();
27 let mut valid = true;
28 match ast.version.as_str() {
29 "1.0.0" => {
30 let version_validator = validate_1_0_0(ast, command);
31 if version_validator.is_err() {
32 valid = false;
33 errors.push(version_validator.err().unwrap());
34 }
35 }
36 "" => {
37 valid = false;
38 errors.push(Error::MissingVersion());
39 }
40 _ => {
41 valid = false;
42 errors.push(Error::InvalidVersion(ast.version.to_string()));
43 }
44 }
45 if valid {
46 Ok(true)
47 } else {
48 Err(Error::Validation(errors))
49 }
50 }
51}
52
53fn validate_1_0_0(ast: &OCAAst, command: Command) -> Result<bool, Error> {
54 let mut valid = true;
60 let mut errors = Vec::new();
61 match (&command.kind, &command.object_kind) {
62 (CommandType::Add, ObjectKind::CaptureBase(_)) => {
63 match rule_add_attr_if_not_exist(ast, command) {
64 Ok(result) => {
65 if !result {
66 valid = result;
67 }
68 }
69 Err(error) => {
70 valid = false;
71 errors.push(error);
72 }
73 }
74 }
75 (CommandType::Remove, ObjectKind::CaptureBase(_)) => {
76 match rule_remove_attr_if_exist(ast, command) {
77 Ok(result) => {
78 if !result {
79 valid = result;
80 }
81 }
82 Err(error) => {
83 valid = false;
84 errors.push(error);
85 }
86 }
87 }
88
89 _ => {
90 }
92 }
93 if valid {
108 Ok(true)
109 } else {
110 Err(Error::Validation(errors))
111 }
112}
113
114fn rule_remove_attr_if_exist(ast: &OCAAst, command_to_validate: Command) -> Result<bool, Error> {
124 let mut errors = Vec::new();
125
126 let attributes = extract_attributes(ast);
127 let properties = extract_properties(ast);
128
129 let content = command_to_validate.object_kind.capture_content();
130
131 println!("attributes: {:?}", attributes);
132 println!("properties: {:?}", properties);
133
134 match (
135 content,
136 content.as_ref().and_then(|c| c.attributes.as_ref()),
137 ) {
138 (Some(_content), Some(attrs_to_remove)) => {
139 println!("attr to remove: {:?}", attrs_to_remove);
140 let valid = attrs_to_remove
141 .keys()
142 .all(|key| attributes.contains_key(key));
143 if !valid {
144 errors.push(Error::InvalidOperation(
145 "Cannot remove attribute if does not exists".to_string(),
146 ));
147 }
148 }
149 (None, None) => (),
150 (None, Some(_)) => (),
151 (Some(_), None) => (),
152 }
153
154 match (
155 content,
156 content.as_ref().and_then(|c| c.properties.as_ref()),
157 ) {
158 (Some(_content), Some(props_to_remove)) => {
159 let valid = props_to_remove
160 .keys()
161 .all(|key| properties.contains_key(key));
162 if !valid {
163 errors.push(Error::InvalidOperation(
164 "Cannot remove property if does not exists".to_string(),
165 ));
166 return Err(Error::Validation(errors));
167 }
168 }
169 (None, None) => (),
170 (None, Some(_)) => (),
171 (Some(_), None) => (),
172 }
173 if errors.is_empty() {
174 Ok(true)
175 } else {
176 Err(Error::Validation(errors))
177 }
178}
179
180fn rule_add_attr_if_not_exist(ast: &OCAAst, command_to_validate: Command) -> Result<bool, Error> {
190 let mut errors = Vec::new();
191 let default_attrs: IndexMap<String, NestedAttrType> = indexmap! {};
193
194 let attributes = extract_attributes(ast);
195
196 let content = command_to_validate.object_kind.capture_content();
197
198 match content {
199 Some(content) => {
200 let attrs_to_add = content.attributes.clone().unwrap_or(default_attrs);
201 debug!("attrs_to_add: {:?}", attrs_to_add);
202
203 let existing_keys: Vec<_> = attrs_to_add
204 .keys()
205 .filter(|key| attributes.contains_key(*key))
206 .collect();
207
208 if !existing_keys.is_empty() {
209 errors.push(Error::InvalidOperation(format!(
210 "Cannot add attribute if already exists: {:?}",
211 existing_keys
212 )));
213 Err(Error::Validation(errors))
214 } else {
215 Ok(true)
216 }
217 }
218 None => {
219 errors.push(Error::InvalidOperation(
220 "No attribtues specify to be added".to_string(),
221 ));
222 Err(Error::Validation(errors))
223 }
224 }
225}
226
227fn extract_attributes(ast: &OCAAst) -> CaptureAttributes {
228 let default_attrs: IndexMap<String, NestedAttrType> = indexmap! {};
229 let mut attributes: CaptureAttributes = indexmap! {};
230 for instruction in &ast.commands {
231 match (instruction.kind.clone(), instruction.object_kind.clone()) {
232 (CommandType::Remove, ObjectKind::CaptureBase(capture_content)) => {
233 let attrs = capture_content
234 .attributes
235 .as_ref()
236 .unwrap_or(&default_attrs);
237 attributes.retain(|key, _value| !attrs.contains_key(key));
238 }
239 (CommandType::Add, ObjectKind::CaptureBase(capture_content)) => {
240 let attrs = capture_content
241 .attributes
242 .as_ref()
243 .unwrap_or(&default_attrs);
244 attributes.extend(attrs.iter().map(|(k, v)| (k.clone(), v.clone())));
245 }
246 _ => {}
247 }
248 }
249 attributes
250}
251
252fn extract_properties(ast: &OCAAst) -> IndexMap<String, NestedValue> {
253 let default_attrs: IndexMap<String, NestedValue> = indexmap! {};
254 let mut properties: IndexMap<String, NestedValue> = indexmap! {};
255 for instruction in &ast.commands {
256 match (instruction.kind.clone(), instruction.object_kind.clone()) {
257 (CommandType::Remove, ObjectKind::CaptureBase(capture_content)) => {
258 let props = capture_content
259 .properties
260 .as_ref()
261 .unwrap_or(&default_attrs);
262 properties.retain(|key, _value| !props.contains_key(key));
263 }
264 (CommandType::Add, ObjectKind::CaptureBase(capture_content)) => {
265 let props = capture_content
266 .properties
267 .as_ref()
268 .unwrap_or(&default_attrs);
269 properties.extend(props.iter().map(|(k, v)| (k.clone(), v.clone())));
270 }
271 _ => {}
272 }
273 }
274 properties
275}
276
277#[cfg(test)]
278mod tests {
279 use indexmap::indexmap;
280
281 use super::*;
282 use crate::ast::{
283 AttributeType, CaptureContent, Command, CommandType, NestedValue, OCAAst, ObjectKind,
284 };
285
286 #[test]
287 fn test_rule_remove_if_exist() {
288 let command = Command {
289 kind: CommandType::Add,
290 object_kind: ObjectKind::CaptureBase(CaptureContent {
291 attributes: Some(indexmap! {
292 "name".to_string() => NestedAttrType::Value(AttributeType::Text),
293 "documentType".to_string() => NestedAttrType::Value(AttributeType::Text),
294 "photo".to_string() => NestedAttrType::Value(AttributeType::Binary),
295 }),
296 properties: Some(indexmap! {
297 "classification".to_string() => NestedValue::Value("GICS:1234".to_string()),
298 }),
299 flagged_attributes: None,
300 }),
301 };
302
303 let command2 = Command {
304 kind: CommandType::Add,
305 object_kind: ObjectKind::CaptureBase(CaptureContent {
306 attributes: Some(indexmap! {
307 "issuer".to_string() => NestedAttrType::Value(AttributeType::Text),
308 "last_name".to_string() => NestedAttrType::Value(AttributeType::Binary),
309 }),
310 properties: Some(indexmap! {
311 "classification".to_string() => NestedValue::Value("GICS:1234".to_string()),
312 }),
313 flagged_attributes: None,
314 }),
315 };
316
317 let remove_command = Command {
318 kind: CommandType::Remove,
319 object_kind: ObjectKind::CaptureBase(CaptureContent {
320 attributes: Some(indexmap! {
321 "name".to_string() => NestedAttrType::Null,
322 "documentType".to_string() => NestedAttrType::Null,
323 }),
324 properties: Some(indexmap! {}),
325 flagged_attributes: None,
326 }),
327 };
328
329 let add_command = Command {
330 kind: CommandType::Add,
331 object_kind: ObjectKind::CaptureBase(CaptureContent {
332 attributes: Some(indexmap! {
333 "name".to_string() => NestedAttrType::Value(AttributeType::Text),
334 }),
335 properties: Some(indexmap! {}),
336 flagged_attributes: None,
337 }),
338 };
339
340 let valid_command = Command {
341 kind: CommandType::Remove,
342 object_kind: ObjectKind::CaptureBase(CaptureContent {
343 attributes: Some(indexmap! {
344 "name".to_string() => NestedAttrType::Null,
345 "issuer".to_string() => NestedAttrType::Null,
346 }),
347 properties: Some(indexmap! {}),
348 flagged_attributes: None,
349 }),
350 };
351
352 let invalid_command = Command {
353 kind: CommandType::Remove,
354 object_kind: ObjectKind::CaptureBase(CaptureContent {
355 attributes: Some(indexmap! {
356 "documentType".to_string() => NestedAttrType::Null,
357 }),
358 properties: Some(indexmap! {}),
359 flagged_attributes: None,
360 }),
361 };
362
363 let mut ocaast = OCAAst::new();
364 ocaast.commands.push(command);
365 ocaast.commands.push(command2);
366 ocaast.commands.push(remove_command);
367 ocaast.commands.push(add_command);
368 let mut result = rule_remove_attr_if_exist(&ocaast, valid_command.clone());
369 assert!(result.is_ok());
370 ocaast.commands.push(invalid_command.clone());
371 result = rule_remove_attr_if_exist(&ocaast, invalid_command);
372 assert!(result.is_err());
373 }
374
375 #[test]
376 fn test_rule_add_if_not_exist() {
377 let command = Command {
378 kind: CommandType::Add,
379 object_kind: ObjectKind::CaptureBase(CaptureContent {
380 attributes: Some(indexmap! {
381 "name".to_string() => NestedAttrType::Value(AttributeType::Text),
382 "documentType".to_string() => NestedAttrType::Value(AttributeType::Text),
383 "photo".to_string() => NestedAttrType::Value(AttributeType::Binary),
384 }),
385 properties: Some(indexmap! {
386 "classification".to_string() => NestedValue::Value("GICS:1234".to_string()),
387 }),
388 flagged_attributes: None,
389 }),
390 };
391
392 let command2 = Command {
393 kind: CommandType::Add,
394 object_kind: ObjectKind::CaptureBase(CaptureContent {
395 attributes: Some(indexmap! {
396 "issuer".to_string() => NestedAttrType::Value(AttributeType::Text),
397 "last_name".to_string() => NestedAttrType::Value(AttributeType::Binary),
398 }),
399 properties: Some(indexmap! {}),
400 flagged_attributes: None,
401 }),
402 };
403
404 let valid_command = Command {
405 kind: CommandType::Add,
406 object_kind: ObjectKind::CaptureBase(CaptureContent {
407 attributes: Some(indexmap! {
408 "first_name".to_string() => NestedAttrType::Value(AttributeType::Text),
409 "address".to_string() => NestedAttrType::Value(AttributeType::Text),
410 }),
411 properties: Some(indexmap! {}),
412 flagged_attributes: None,
413 }),
414 };
415
416 let invalid_command = Command {
417 kind: CommandType::Add,
418 object_kind: ObjectKind::CaptureBase(CaptureContent {
419 attributes: Some(indexmap! {
420 "name".to_string() => NestedAttrType::Value(AttributeType::Text),
421 "phone".to_string() => NestedAttrType::Value(AttributeType::Text),
422 }),
423 properties: Some(indexmap! {}),
424 flagged_attributes: None,
425 }),
426 };
427
428 let mut ocaast = OCAAst::new();
429 ocaast.commands.push(command);
430 ocaast.commands.push(command2);
431 let mut result = rule_add_attr_if_not_exist(&ocaast, valid_command.clone());
432 assert!(result.is_ok());
433 ocaast.commands.push(invalid_command.clone());
434 result = rule_add_attr_if_not_exist(&ocaast, invalid_command.clone());
435 assert!(result.is_err());
436 }
437}