Skip to main content

globetrotter_model/
validation.rs

1use crate::{
2    Language, TemplateEngine, Translation, Translations,
3    diagnostics::{DiagnosticExt, FileId, Spanned},
4};
5use codespan_reporting::diagnostic::{Diagnostic, Label};
6use itertools::Itertools;
7
8fn validate_handlebars_template(translation: &Translation, errors: &mut Vec<Diagnostic<FileId>>) {
9    errors.extend(
10        translation
11            .language
12            .iter()
13            .filter_map(|(language, template)| {
14                tracing::trace!(
15                    lang = ?language,
16                    template = template.as_ref(),
17                    engine = ?TemplateEngine::Handlebars,
18                    "validating",
19                );
20                match handlebars::template::Template::compile(template.as_ref()) {
21                    Ok(_) => None,
22                    Err(err) => {
23                        let diagnostic = Diagnostic::error()
24                            .with_message("handlebars template failed to compile")
25                            .with_labels(vec![
26                                Label::primary(translation.file_id, template.span.clone())
27                                    .with_message(err.to_string()),
28                            ]);
29                        Some(diagnostic)
30                    }
31                }
32            }),
33    );
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct ValidationOptions<'a> {
38    pub required_languages: &'a [Spanned<Language>],
39    pub template_engine: Option<&'a Spanned<TemplateEngine>>,
40    pub strict: bool,
41    pub check_templates: bool,
42}
43
44impl Translations {
45    #[cfg(feature = "rayon")]
46    pub fn validate(
47        &self,
48        config_name: &Spanned<String>,
49        config_file_id: Option<FileId>,
50        diagnostics: &mut Vec<Diagnostic<FileId>>,
51        options: &ValidationOptions<'_>,
52    ) {
53        use rayon::prelude::*;
54
55        tracing::trace!(
56            num_translations = self.0.len(),
57            languages = ?options
58                .required_languages
59                .iter()
60                .map(Spanned::as_ref)
61                .collect::<Vec<_>>(),
62            check_templates = options.check_templates,
63            "validating",
64        );
65        let partial_diagnostics = self.0.par_iter().flat_map(|(key, translation)| {
66            let mut diagnostics = vec![];
67            diagnostics.extend(options.required_languages.iter().unique().filter_map(|lang| {
68                #[allow(clippy::if_same_then_else)]
69                if translation.language.contains_key(lang.as_ref()) {
70                    None
71                } else {
72                    None
73                    // Some(ValidationError::MissingLanguage {
74                    //     key: key.to_string(),
75                    //     language: *lang,
76                    // })
77                }
78            }));
79
80            if options.check_templates && translation.is_template() {
81                // Check that templates compile
82                match options.template_engine {
83                    None => {
84                        let message = format!(
85                            "running with `--check`, but no template engine is specified for `{config_name}`",
86                        );
87                        let diagnostic =
88                            Diagnostic::warning_or_error(options.strict).with_message(message);
89                        diagnostics.push(diagnostic);
90                    }
91                    Some(Spanned {
92                        inner: TemplateEngine::Handlebars,
93                        ..
94                    }) => validate_handlebars_template(translation, &mut diagnostics),
95                    Some(other) => {
96                        let mut diagnostic = Diagnostic::error().with_message(format!(
97                            "unsupported template engine {:?}",
98                            other.as_ref()
99                        ));
100                        if let Some(config_file_id) = config_file_id {
101                            diagnostic = diagnostic.with_labels(vec![Label::primary(
102                                config_file_id,
103                                other.span.clone(),
104                            )
105                            .with_message(format!(
106                                "`--check` is not supported for template engine {:?}",
107                                other.as_ref()
108                            ))]);
109                        }
110                        diagnostics.push(diagnostic);
111                    }
112                }
113            }
114            diagnostics
115        });
116
117        diagnostics.extend(partial_diagnostics.collect::<Vec<_>>());
118    }
119}