globetrotter_model/
validation.rs1use 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 }
78 }));
79
80 if options.check_templates && translation.is_template() {
81 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}