1use crate::state::oca::overlay::Overlay;
2use crate::state::oca::DynOverlay;
3use indexmap::IndexMap;
4use isolang::Language;
5use oca_ast::ast::{AttributeType, NestedAttrType, OverlayType};
6use std::{
7    collections::{HashMap, HashSet},
8    error::Error as StdError,
9};
10
11use super::oca::{overlay, OCABundle};
12use piccolo::{Closure, Lua, Thread};
13
14#[derive(Debug)]
15pub enum Error {
16    Custom(String),
17    MissingTranslations(Language),
18    MissingMetaTranslation(Language, String),
19    UnexpectedTranslations(Language),
20    MissingAttributeTranslation(Language, String),
21}
22
23impl std::fmt::Display for Error {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Error::Custom(error) => write!(f, "{error}"),
27            Error::MissingTranslations(language) => {
28                write!(f, "Missing translation in {language} language")
29            }
30            Error::MissingMetaTranslation(language, attr) => write!(
31                f,
32                "Missing meta translation for {attr} in {language} language"
33            ),
34            Error::UnexpectedTranslations(language) => {
35                write!(f, "Unexpected translations in {language} language")
36            }
37            Error::MissingAttributeTranslation(language, attr) => {
38                write!(f, "Missing translation for {attr} in {language} language")
39            }
40        }
41    }
42}
43
44impl std::error::Error for Error {}
45
46pub struct Validator {
47    enforced_translations: Vec<Language>,
48}
49
50impl Default for Validator {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl Validator {
57    pub fn new() -> Validator {
58        Validator {
59            enforced_translations: vec![],
60        }
61    }
62
63    pub fn enforce_translations(mut self, languages: Vec<Language>) -> Validator {
64        self.enforced_translations = self
65            .enforced_translations
66            .into_iter()
67            .chain(languages)
68            .collect::<Vec<Language>>();
69        self
70    }
71
72    pub fn validate(self, oca_bundle: &OCABundle) -> Result<(), Vec<Error>> {
73        let enforced_langs: HashSet<_> = self.enforced_translations.iter().collect();
74        let mut errors: Vec<Error> = vec![];
75
76        let mut recalculated_oca_bundle = oca_bundle.clone();
80        recalculated_oca_bundle.fill_said();
81
82        if oca_bundle.said.ne(&recalculated_oca_bundle.said) {
83            errors.push(Error::Custom("OCA Bundle: Malformed SAID".to_string()));
84        }
85
86        let capture_base = &oca_bundle.capture_base;
87
88        let mut recalculated_capture_base = capture_base.clone();
89        recalculated_capture_base.sign();
90
91        if capture_base.said.ne(&recalculated_capture_base.said) {
92            errors.push(Error::Custom("capture_base: Malformed SAID".to_string()));
93        }
94
95        for o in &oca_bundle.overlays {
96            let mut recalculated_overlay = o.clone();
97            recalculated_overlay.fill_said();
98            if o.said().ne(recalculated_overlay.said()) {
99                let msg = match o.language() {
100                    Some(lang) => format!("{} ({}): Malformed SAID", o.overlay_type(), lang),
101                    None => format!("{}: Malformed SAID", o.overlay_type()),
102                };
103                errors.push(Error::Custom(msg));
104            }
105
106            if o.capture_base().ne(&capture_base.said) {
107                let msg = match o.language() {
108                    Some(lang) => {
109                        format!("{} ({}): Mismatch capture_base SAI", o.overlay_type(), lang)
110                    }
111                    None => format!("{}: Mismatch capture_base SAI", o.overlay_type()),
112                };
113                errors.push(Error::Custom(msg));
114            }
115        }
116
117        let conditional_overlay = oca_bundle
118            .overlays
119            .iter()
120            .find_map(|x| x.as_any().downcast_ref::<overlay::Conditional>());
121
122        if let Some(conditional_overlay) = conditional_overlay {
123            self.validate_conditional(
124                oca_bundle.capture_base.attributes.clone(),
125                conditional_overlay,
126            )?;
127        }
128
129        if !enforced_langs.is_empty() {
130            let meta_overlays = oca_bundle
131                .overlays
132                .iter()
133                .filter_map(|x| x.as_any().downcast_ref::<overlay::Meta>())
134                .collect::<Vec<_>>();
135
136            if !meta_overlays.is_empty() {
137                if let Err(meta_errors) = self.validate_meta(&enforced_langs, meta_overlays) {
138                    errors = errors
139                        .into_iter()
140                        .chain(meta_errors.into_iter().map(|e| {
141                            if let Error::UnexpectedTranslations(lang) = e {
142                                Error::Custom(format!(
143                                    "meta overlay: translations in {lang:?} language are not enforced"
144                                ))
145                            } else if let Error::MissingTranslations(lang) = e {
146                                Error::Custom(format!(
147                                    "meta overlay: translations in {lang:?} language are missing"
148                                ))
149                            } else if let Error::MissingMetaTranslation(lang, attr) = e {
150                                Error::Custom(format!(
151                                    "meta overlay: for '{attr}' translation in {lang:?} language is missing"
152                                ))
153                            } else {
154                                e
155                            }
156                        }))
157                        .collect();
158                }
159            }
160
161            for overlay_type in &[
162                OverlayType::Entry,
163                OverlayType::Information,
164                OverlayType::Label,
165            ] {
166                let typed_overlays: Vec<_> = oca_bundle
167                    .overlays
168                    .iter()
169                    .filter(|x| x.overlay_type().eq(overlay_type))
170                    .collect();
171                if typed_overlays.is_empty() {
172                    continue;
173                }
174
175                if let Err(translation_errors) =
176                    self.validate_translations(&enforced_langs, typed_overlays)
177                {
178                    errors = errors.into_iter().chain(
179                        translation_errors.into_iter().map(|e| {
180                            if let Error::UnexpectedTranslations(lang) = e {
181                                Error::Custom(
182                                    format!("{overlay_type} overlay: translations in {lang:?} language are not enforced")
183                                )
184                            } else if let Error::MissingTranslations(lang) = e {
185                                Error::Custom(
186                                    format!("{overlay_type} overlay: translations in {lang:?} language are missing")
187                                )
188                            } else if let Error::MissingAttributeTranslation(lang, attr_name) = e {
189                                Error::Custom(
190                                    format!("{overlay_type} overlay: for '{attr_name}' attribute missing translations in {lang:?} language")
191                                )
192                            } else {
193                                e
194                            }
195                        })
196                    ).collect();
197                }
198            }
199        }
200
201        if errors.is_empty() {
202            Ok(())
203        } else {
204            Err(errors)
205        }
206    }
207
208    fn validate_conditional(
209        &self,
210        attr_types: IndexMap<String, NestedAttrType>,
211        overlay: &overlay::Conditional,
212    ) -> Result<(), Vec<Error>> {
213        let mut errors: Vec<Error> = vec![];
214
215        let conditions = overlay.attribute_conditions.clone();
216        let dependencies = overlay.attribute_dependencies.clone();
217        let re = regex::Regex::new(r"\$\{(\d+)\}").unwrap();
218        for &attr in overlay.attributes().iter() {
219            let condition = conditions.get(attr).unwrap(); let condition_dependencies = dependencies.get(attr).unwrap(); if condition_dependencies.contains(attr) {
222                errors.push(Error::Custom(format!(
223                    "Attribute '{attr}' cannot be a dependency of itself"
224                )));
225                continue;
226            }
227
228            let mut attr_mocks: HashMap<String, String> = HashMap::new();
229            condition_dependencies.iter().for_each(|dep| {
230                let dep_type = attr_types.get(dep).unwrap(); let value = match dep_type {
232                    NestedAttrType::Null => "null".to_string(),
233                    NestedAttrType::Value(base_type) => match base_type {
234                        AttributeType::Text => "'test'".to_string(),
235                        AttributeType::Numeric => "0".to_string(),
236                        AttributeType::DateTime => "'2020-01-01'".to_string(),
237                        AttributeType::Binary => "test".to_string(),
238                        AttributeType::Boolean => "true".to_string(),
239                    },
240                    NestedAttrType::Array(boxed_type) => match **boxed_type {
242                        NestedAttrType::Value(base_type) => match base_type {
243                            AttributeType::Text => "['test']".to_string(),
244                            AttributeType::Numeric => "[0]".to_string(),
245                            AttributeType::DateTime => "['2020-01-01']".to_string(),
246                            AttributeType::Binary => "[test]".to_string(),
247                            AttributeType::Boolean => "[true]".to_string(),
248                        },
249                        _ => panic!("Invalid or not supported array type"),
250                    },
251                    NestedAttrType::Reference(ref_value) => ref_value.to_string(),
252                };
253                attr_mocks.insert(dep.to_string(), value);
254            });
255
256            let script = re
257                .replace_all(condition, |caps: ®ex::Captures| {
258                    attr_mocks
259                        .get(&condition_dependencies[caps[1].parse::<usize>().unwrap()].clone())
260                        .unwrap()
261                        .to_string()
262                })
263                .to_string();
264
265            let mut lua = Lua::new();
266            let thread_result = lua.try_run(|ctx| {
267                let closure = Closure::load(ctx, format!("return {script}").as_bytes())?;
268                let thread = Thread::new(&ctx);
269                thread.start(ctx, closure.into(), ())?;
270                Ok(ctx.state.registry.stash(&ctx, thread))
271            });
272
273            match thread_result {
274                Ok(thread) => {
275                    if let Err(e) = lua.run_thread::<bool>(&thread) {
276                        errors.push(Error::Custom(format!(
277                            "Attribute '{attr}' has invalid condition: {}",
278                            e.source().unwrap()
279                        )));
280                    }
281                }
282                Err(e) => {
283                    errors.push(Error::Custom(format!(
284                        "Attribute '{attr}' has invalid condition: {}",
285                        e.source().unwrap()
286                    )));
287                }
288            }
289        }
290
291        if errors.is_empty() {
292            Ok(())
293        } else {
294            Err(errors)
295        }
296    }
297
298    fn validate_meta(
299        &self,
300        enforced_langs: &HashSet<&Language>,
301        meta_overlays: Vec<&overlay::Meta>,
302    ) -> Result<(), Vec<Error>> {
303        let mut errors: Vec<Error> = vec![];
304        let translation_langs: HashSet<_> = meta_overlays
305            .iter()
306            .map(|o| o.language().unwrap())
307            .collect();
308
309        let missing_enforcement: HashSet<&_> =
310            translation_langs.difference(enforced_langs).collect();
311        for m in missing_enforcement {
312            errors.push(Error::UnexpectedTranslations(**m));
313        }
314
315        let missing_translations: HashSet<&_> =
316            enforced_langs.difference(&translation_langs).collect();
317        for m in missing_translations {
318            errors.push(Error::MissingTranslations(**m));
319        }
320
321        let attributes = meta_overlays
322            .iter()
323            .flat_map(|o| o.attr_pairs.keys())
324            .collect::<HashSet<_>>();
325
326        for meta_overlay in meta_overlays {
327            attributes.iter().for_each(|attr| {
328                if !meta_overlay.attr_pairs.contains_key(*attr) {
329                    errors.push(Error::MissingMetaTranslation(
330                        *meta_overlay.language().unwrap(),
331                        attr.to_string(),
332                    ));
333                }
334            });
335        }
336
337        if errors.is_empty() {
338            Ok(())
339        } else {
340            Err(errors)
341        }
342    }
343
344    fn validate_translations(
345        &self,
346        enforced_langs: &HashSet<&Language>,
347        overlays: Vec<&DynOverlay>,
348    ) -> Result<(), Vec<Error>> {
349        let mut errors: Vec<Error> = vec![];
350
351        let overlay_langs: HashSet<_> = overlays.iter().map(|x| x.language().unwrap()).collect();
352
353        let missing_enforcement: HashSet<&_> = overlay_langs.difference(enforced_langs).collect();
354        for m in missing_enforcement {
355            errors.push(Error::UnexpectedTranslations(**m)); }
357
358        let missing_translations: HashSet<&_> = enforced_langs.difference(&overlay_langs).collect();
359        for m in missing_translations {
360            errors.push(Error::MissingTranslations(**m)); }
362
363        let all_attributes: HashSet<&String> =
364            overlays.iter().flat_map(|o| o.attributes()).collect();
365        for overlay in overlays.iter() {
366            let attributes: HashSet<_> = overlay.attributes().into_iter().collect();
367
368            let missing_attr_translation: HashSet<&_> =
369                all_attributes.difference(&attributes).collect();
370            for m in missing_attr_translation {
371                errors.push(Error::MissingAttributeTranslation(
372                    *overlay.language().unwrap(),
373                    m.to_string(),
374                ));
375            }
376        }
377
378        if errors.is_empty() {
379            Ok(())
380        } else {
381            Err(errors)
382        }
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use crate::controller::load_oca;
390    use crate::state::{
391        attribute::{Attribute, AttributeType},
392        encoding::Encoding,
393        oca::overlay::character_encoding::CharacterEncodings,
394        oca::overlay::conditional::Conditionals,
395        oca::overlay::label::Labels,
396        oca::overlay::meta::Metas,
397        oca::OCABox,
398    };
399
400    #[test]
401    fn validate_valid_oca() {
402        let validator = Validator::new().enforce_translations(vec![Language::Eng, Language::Pol]);
403
404        let mut oca = cascade! {
405            OCABox::new();
406            ..add_meta(Language::Eng, "name".to_string(), "Driving Licence".to_string());
407            ..add_meta(Language::Eng, "description".to_string(), "DL".to_string());
408            ..add_meta(Language::Pol, "name".to_string(), "Prawo Jazdy".to_string());
409            ..add_meta(Language::Pol, "description".to_string(), "PJ".to_string());
410        };
411
412        let attribute = cascade! {
413            Attribute::new("name".to_string());
414            ..set_attribute_type(NestedAttrType::Value(AttributeType::Text));
415            ..set_encoding(Encoding::Utf8);
416            ..set_label(Language::Eng, "Name: ".to_string());
417            ..set_label(Language::Pol, "ImiÄ™: ".to_string());
418        };
419
420        oca.add_attribute(attribute);
421
422        let attribute_2 = cascade! {
423            Attribute::new("age".to_string());
424            ..set_attribute_type(NestedAttrType::Value(AttributeType::Numeric));
425            ..set_label(Language::Eng, "Age: ".to_string());
426            ..set_label(Language::Pol, "Wiek: ".to_string());
427        };
428
429        oca.add_attribute(attribute_2);
430
431        let oca_bundle = oca.generate_bundle();
432
433        let result = validator.validate(&oca_bundle);
434
435        if let Err(ref errors) = result {
436            println!("{errors:?}");
437        }
438        assert!(result.is_ok());
439    }
440
441    #[test]
442    fn validate_oca_with_missing_name_translation() {
443        let validator = Validator::new().enforce_translations(vec![Language::Eng, Language::Pol]);
444
445        let mut oca = cascade! {
446            OCABox::new();
447            ..add_meta(Language::Eng, "name".to_string(), "Driving Licence".to_string());
448        };
449
450        let oca_bundle = oca.generate_bundle();
451
452        let result = validator.validate(&oca_bundle);
453
454        assert!(result.is_err());
455        if let Err(errors) = result {
456            assert_eq!(errors.len(), 1);
457        }
458    }
459
460    #[test]
461    fn validate_oca_with_standards() {
462        }
479
480    #[test]
481    fn validate_oca_with_invalid_saids() {
482        let validator = Validator::new();
483        let data = r#"
484{
485    "version": "OCAB10000023_",
486    "said": "EBQMQm_tXSC8tnNICl7paGUeGg0SyF1tceHhTUutn1PN",
487    "capture_base": {
488        "type": "spec/capture_base/1.0",
489        "said": "EBQMQm_tXSC8tnNICl7paGUeGg0SyF1tceHhTUutn1PN",
490        "classification": "",
491        "attributes": {
492            "n1": "Text",
493            "n2": "DateTime",
494            "n3": "refs:EBQMQm_tXSC8tnNICl7paGUeGg0SyF1tceHhTUutn1aP"
495        },
496        "flagged_attributes": ["n1"]
497    },
498    "overlays": {
499        "character_encoding": {
500            "capture_base": "EDRt2wL8yVWVSJdF8aMFtU9VQ6aWzXZTgWj3WqsIKLqm",
501            "said": "EBQMQm_tXSC8tnNICl7paGUeGg0SyF1tceHhTUutn1PN",
502            "type": "spec/overlays/character_encoding/1.0",
503            "default_character_encoding": "utf-8",
504            "attribute_character_encoding": {}
505        }
506    }
507}
508        "#;
509        let oca_bundle = load_oca(&mut data.as_bytes());
510        match oca_bundle {
511            Ok(oca_bundle) => {
512                let result = validator.validate(&oca_bundle);
513                assert!(result.is_err());
514                if let Err(errors) = result {
515                    println!("{:?}", errors);
516                    assert_eq!(errors.len(), 4);
517                }
518            }
519            Err(e) => {
520                println!("{:?}", e);
521                panic!("Failed to load OCA bundle");
522            }
523        }
524    }
525
526    #[test]
527    fn validate_oca_with_conditional() {
528        let validator = Validator::new();
529
530        let mut oca = OCABox::new();
531
532        let attribute_age = cascade! {
533            Attribute::new("age".to_string());
534            ..set_attribute_type(NestedAttrType::Value(AttributeType::Numeric));
535            ..set_encoding(Encoding::Utf8);
536        };
537
538        oca.add_attribute(attribute_age);
539
540        let attribute_name = cascade! {
541            Attribute::new("name".to_string());
542            ..set_attribute_type(NestedAttrType::Value(AttributeType::Text));
543            ..set_condition(
544                "${age} > 18 and ${age} < 30".to_string()
545            );
546        };
547
548        oca.add_attribute(attribute_name);
549
550        let oca_bundle = oca.generate_bundle();
551        let result = validator.validate(&oca_bundle);
552        assert!(result.is_ok());
553
554        }
560}