nimbus_fml/editing/
error_converter.rs
1use super::{values_finder::ValuesFinder, ErrorKind, FeatureValidationError};
6#[cfg(feature = "client-lib")]
7use super::{CorrectionCandidate, FmlEditorError};
8use crate::{
9 error::FMLError,
10 intermediate_representation::{EnumDef, FeatureDef, ObjectDef},
11};
12use serde_json::Value;
13use std::collections::{BTreeMap, BTreeSet};
14
15#[allow(dead_code)]
16pub(crate) struct ErrorConverter<'a> {
17 pub(crate) enum_defs: &'a BTreeMap<String, EnumDef>,
18 pub(crate) object_defs: &'a BTreeMap<String, ObjectDef>,
19}
20
21impl<'a> ErrorConverter<'a> {
22 pub(crate) fn new(
23 enum_defs: &'a BTreeMap<String, EnumDef>,
24 object_defs: &'a BTreeMap<String, ObjectDef>,
25 ) -> Self {
26 Self {
27 enum_defs,
28 object_defs,
29 }
30 }
31
32 pub(crate) fn convert_feature_error(
33 &self,
34 feature_def: &FeatureDef,
35 feature_value: &Value,
36 error: FeatureValidationError,
37 ) -> FMLError {
38 let values = ValuesFinder::new(self.enum_defs, feature_def, feature_value);
39 let long_message = self.long_message(&values, &error);
40 FMLError::ValidationError(error.path.path, long_message)
41 }
42
43 #[allow(dead_code)]
44 #[cfg(feature = "client-lib")]
45 pub(crate) fn convert_into_editor_errors(
46 &self,
47 feature_def: &FeatureDef,
48 feature_value: &Value,
49 src: &str,
50 errors: &Vec<FeatureValidationError>,
51 ) -> Vec<FmlEditorError> {
52 let mut editor_errors: Vec<_> = Default::default();
53 let values = ValuesFinder::new(self.enum_defs, feature_def, feature_value);
54 for error in errors {
55 let message = self.long_message(&values, error);
58 let highlight = error.path.first_error_token().map(String::from);
62 let error_span = error.path.error_span(src);
64
65 let corrections = self.correction_candidates(&values, src, error);
66
67 let error = FmlEditorError {
68 message,
69
70 highlight,
71 corrections,
72
73 line: error_span.from.line,
75 col: error_span.from.col,
76
77 error_span,
78 };
79 editor_errors.push(error);
80 }
81 editor_errors
82 }
83
84 pub(crate) fn convert_object_error(&self, error: FeatureValidationError) -> FMLError {
85 FMLError::ValidationError(error.path.path.to_owned(), self.message(&error))
86 }
87}
88
89impl ErrorConverter<'_> {
90 fn long_message(&self, values: &ValuesFinder, error: &FeatureValidationError) -> String {
91 let message = self.message(error);
92 let mut suggestions = self.string_replacements(error, values);
93 let dym = did_you_mean(&mut suggestions);
94 format!("{message}{dym}")
95 }
96
97 fn message(&self, error: &FeatureValidationError) -> String {
98 let token = error.path.error_token_abbr();
99 error.kind.message(&token)
100 }
101
102 #[allow(dead_code)]
103 #[cfg(feature = "client-lib")]
104 fn correction_candidates(
105 &self,
106 values: &ValuesFinder,
107 _src: &str,
108 error: &FeatureValidationError,
109 ) -> Vec<CorrectionCandidate> {
110 let strings = self.string_replacements(error, values);
111 let placeholders = self.placeholder_replacements(error, values);
112
113 let mut candidates = Vec::with_capacity(strings.len() + placeholders.len());
114 for s in &strings {
115 candidates.push(CorrectionCandidate::string_replacement(s));
116 }
117 for s in &placeholders {
118 candidates.push(CorrectionCandidate::literal_replacement(s));
119 }
120 candidates
121 }
122}
123
124impl ErrorConverter<'_> {
127 #[allow(dead_code)]
128 #[cfg(feature = "client-lib")]
129 fn placeholder_replacements(
130 &self,
131 error: &FeatureValidationError,
132 values: &ValuesFinder,
133 ) -> BTreeSet<String> {
134 match &error.kind {
135 ErrorKind::InvalidValue { value_type: t, .. }
136 | ErrorKind::TypeMismatch { value_type: t }
137 | ErrorKind::InvalidNestedValue { prop_type: t, .. } => values.all_placeholders(t),
138 _ => Default::default(),
139 }
140 }
141
142 fn string_replacements(
143 &self,
144 error: &FeatureValidationError,
145 values: &ValuesFinder,
146 ) -> BTreeSet<String> {
147 let complete = match &error.kind {
148 ErrorKind::InvalidKey { key_type: t, .. }
149 | ErrorKind::InvalidValue { value_type: t, .. }
150 | ErrorKind::TypeMismatch { value_type: t } => values.all_specific_strings(t),
151 ErrorKind::InvalidPropKey { valid, .. } => valid
155 .iter()
156 .filter(|s| s.starts_with(char::is_alphanumeric))
157 .map(ToOwned::to_owned)
158 .collect(),
159 ErrorKind::InvalidNestedValue { .. } => Default::default(),
160 };
161
162 match &error.kind {
165 ErrorKind::InvalidKey { in_use, .. } | ErrorKind::InvalidPropKey { in_use, .. }
166 if !complete.is_disjoint(in_use) =>
170 {
171 complete.difference(in_use).cloned().collect()
172 }
173 _ => complete,
174 }
175 }
176}
177
178fn did_you_mean(words: &mut BTreeSet<String>) -> String {
179 let mut words = words.iter();
180 match words.len() {
181 0 => String::from(""),
182 1 => format!("; did you mean \"{}\"?", words.next().unwrap()),
183 2 => format!(
184 "; did you mean \"{}\" or \"{}\"?",
185 words.next().unwrap(),
186 words.next().unwrap(),
187 ),
188 _ => {
189 let last = words.next_back().unwrap();
190 format!(
191 "; did you mean one of \"{}\" or \"{last}\"?",
192 itertools::join(words, "\", \"")
193 )
194 }
195 }
196}