1use crate::{
25 attribute::Attribute,
26 datamodel::DataModel,
27 markdown::{frontmatter::FrontMatter, position::Position},
28 object::{Enumeration, Object},
29 xmltype::XMLType,
30};
31use colored::Colorize;
32use log::error;
33use serde::{Deserialize, Serialize};
34use std::collections::{HashMap, HashSet};
35use std::error::Error;
36use std::fmt::{Display, Formatter};
37
38#[cfg(feature = "wasm")]
39use tsify_next::Tsify;
40
41pub(crate) const BASIC_TYPES: [&str; 7] = [
43 "string", "number", "integer", "boolean", "float", "date", "bytes",
44];
45
46#[derive(Debug, Clone, Serialize, PartialEq)]
48#[cfg_attr(feature = "wasm", derive(Tsify))]
49#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
50pub struct ValidationError {
51 pub message: String,
52 pub object: Option<String>,
53 pub attribute: Option<String>,
54 pub location: String,
55 pub solution: Option<String>,
56 pub error_type: ErrorType,
57 pub positions: Vec<Position>,
58}
59
60impl Display for ValidationError {
61 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
63 let lines: Vec<String> = self.positions.iter().map(|p| p.line.to_string()).collect();
64 let mut line = lines.join(", ");
65
66 if !lines.is_empty() {
67 line = format!("[line: {line}]");
68 } else {
69 line = "".to_string();
70 }
71
72 write!(
73 f,
74 "{}[{}{}] {}:\n\tâââ {}\n\t {}",
75 line,
76 self.object.clone().unwrap_or("Global".into()).bold(),
77 match &self.attribute {
78 Some(attr) => format!(".{attr}"),
79 None => "".into(),
80 },
81 self.error_type.to_string().bold(),
82 self.message.red().bold(),
83 self.solution.clone().unwrap_or("".into()).yellow().bold(),
84 )?;
85 Ok(())
86 }
87}
88
89#[derive(Debug, Clone, Serialize, PartialEq, Deserialize)]
91#[cfg_attr(feature = "wasm", derive(Tsify))]
92#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
93pub enum ErrorType {
94 NameError,
95 TypeError,
96 DuplicateError,
97 GlobalError,
98 XMLError,
99 ObjectError,
100}
101
102impl Display for ErrorType {
103 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105 match self {
106 ErrorType::NameError => write!(f, "NameError"),
107 ErrorType::TypeError => write!(f, "TypeError"),
108 ErrorType::DuplicateError => write!(f, "DuplicateError"),
109 ErrorType::GlobalError => write!(f, "GlobalError"),
110 ErrorType::XMLError => write!(f, "XMLError"),
111 ErrorType::ObjectError => write!(f, "ObjectError"),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, PartialEq)]
118#[cfg_attr(feature = "wasm", derive(Tsify))]
119#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
120pub struct Validator {
121 pub is_valid: bool,
122 pub errors: Vec<ValidationError>,
123 #[serde(skip_serializing)]
124 pub object_positions: HashMap<String, Vec<Position>>,
125 #[serde(skip_serializing)]
126 pub enum_positions: HashMap<String, Vec<Position>>,
127}
128
129impl Error for Validator {}
130
131impl Display for Validator {
132 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134 for error in &self.errors {
135 error.fmt(f)?;
136 }
137 Ok(())
138 }
139}
140
141impl Validator {
142 pub fn new() -> Self {
144 Self {
145 is_valid: true,
146 errors: vec![],
147 object_positions: HashMap::new(),
148 enum_positions: HashMap::new(),
149 }
150 }
151 pub fn reset(&mut self) {
152 self.is_valid = true;
153 self.errors.clear();
154 self.object_positions.clear();
155 self.enum_positions.clear();
156 }
157
158 pub fn add_error(&mut self, error: ValidationError) {
164 self.errors.push(error);
165 self.is_valid = false;
166 }
167
168 pub fn log_result(&self) {
172 for error in &self.errors {
173 error!("{}", error);
174 }
175 }
176
177 pub fn validate(&mut self, model: &DataModel) {
189 self.reset();
191
192 self.object_positions = extract_object_positions(model);
194 self.enum_positions = extract_enum_positions(model);
195
196 let types = Self::extract_type_names(model);
198
199 self.check_duplicate_objects(&model.objects);
201 self.check_duplicate_enums(&model.enums);
202 self.check_has_no_objects(model);
203
204 for object in &model.objects {
206 self.validate_object(object, &types, &model.clone().config.unwrap_or_default());
207 }
208
209 self.sort_errors();
210 }
211
212 fn check_duplicate_objects(&mut self, collection: &[Object]) {
218 let unique = collection
219 .iter()
220 .map(|object| object.name.as_str())
221 .collect::<Vec<&str>>();
222
223 let duplicates = unique_elements(&get_duplicates(&unique));
224
225 if !duplicates.is_empty() {
226 for name in duplicates {
227 self.add_error(ValidationError {
228 message: format!("Object '{name}' is defined more than once."),
229 object: Some(name.to_string()),
230 attribute: None,
231 location: "Global".into(),
232 error_type: ErrorType::DuplicateError,
233 solution: Some(format!(
234 "Rename the object(s) at lines {} to be unique.",
235 get_line_numbers(self.object_positions.get(name).unwrap_or(&vec![]))
236 )),
237 positions: self
238 .object_positions
239 .get(name)
240 .cloned()
241 .unwrap_or_default()
242 .clone(),
243 });
244 }
245 }
246 }
247
248 fn check_duplicate_enums(&mut self, collection: &[Enumeration]) {
254 let unique = collection
255 .iter()
256 .map(|object| object.name.as_str())
257 .collect::<Vec<&str>>();
258
259 let duplicates = unique
261 .iter()
262 .cloned()
263 .filter(|&name| unique.iter().filter(|&n| n == &name).count() > 1)
264 .collect::<Vec<&str>>();
265
266 let duplicates = unique_elements(&duplicates);
267
268 if !duplicates.is_empty() {
269 for name in duplicates {
270 self.add_error(ValidationError {
271 message: format!("Enumeration '{name}' is defined more than once."),
272 object: Some(name.to_string()),
273 attribute: None,
274 location: "Global".into(),
275 error_type: ErrorType::DuplicateError,
276 solution: Some(format!(
277 "Rename the enumeration(s) at lines {} to be unique.",
278 get_line_numbers(self.enum_positions.get(name).unwrap_or(&vec![]))
279 )),
280 positions: self.enum_positions.get(name).cloned().unwrap_or_default(),
281 });
282 }
283 }
284 }
285
286 fn validate_object(&mut self, object: &Object, types: &[&str], frontmatter: &FrontMatter) {
294 self.validate_object_name(&object.name);
295
296 if !frontmatter.allow_empty {
297 self.check_has_attributes(object);
298 }
299
300 self.check_duplicate_attributes(object);
301
302 object.attributes.iter().for_each(|attribute| {
304 self.validate_attribute(attribute, types, object);
305 });
306 }
307
308 fn check_duplicate_attributes(&mut self, object: &Object) {
314 let attr_names = object
316 .attributes
317 .iter()
318 .map(|attribute| attribute.name.as_str())
319 .collect::<Vec<&str>>();
320
321 let attribute_positions = extract_attribute_positions(object);
322
323 let unique = unique_elements(&attr_names);
324 if attr_names.len() != unique.len() {
325 let duplicates = unique_elements(&get_duplicates(&attr_names));
326
327 for name in duplicates {
328 self.add_error(ValidationError {
329 message: format!("Property '{name}' is defined more than once."),
330 object: Some(object.name.clone()),
331 attribute: Some(name.to_string()),
332 location: "Global".into(),
333 error_type: ErrorType::DuplicateError,
334 solution: Some(format!(
335 "Rename the property(ies) at lines {} to be unique.",
336 get_line_numbers(attribute_positions.get(name).unwrap_or(&vec![]))
337 )),
338 positions: attribute_positions.get(name).cloned().unwrap_or_default(),
339 });
340 }
341 }
342 }
343
344 fn check_has_attributes(&mut self, object: &Object) {
350 if !object.has_attributes() {
351 self.add_error(ValidationError {
352 message: format!("Type '{}' is empty and has no properties.", object.name),
353 object: Some(object.name.clone()),
354 attribute: None,
355 location: "Global".into(),
356 error_type: ErrorType::ObjectError,
357 solution: Some(format!("Add a property to the object '{}'.", object.name)),
358 positions: self
359 .object_positions
360 .get(&object.name)
361 .cloned()
362 .unwrap_or_default(),
363 });
364 }
365 }
366
367 fn validate_object_name(&mut self, name: &str) {
373 let checks = vec![starts_with_character, contains_white_space, |name: &str| {
374 contains_special_characters(name, false)
375 }];
376
377 for check in checks {
378 if let Err((e, solution)) = check(name) {
379 self.add_error(ValidationError {
380 message: e,
381 object: Some(name.to_string()),
382 attribute: None,
383 solution: Some(format!("Resolve the issue by using '{solution}'.")),
384 location: "Global".into(),
385 error_type: ErrorType::NameError,
386 positions: self.object_positions.get(name).cloned().unwrap_or_default(),
387 });
388 }
389 }
390 }
391
392 fn check_has_no_objects(&mut self, model: &DataModel) {
398 if model.objects.is_empty() {
399 self.add_error(ValidationError {
400 message: "This model has no definitions.".into(),
401 object: Some("Model".into()),
402 attribute: None,
403 solution: Some("Add an object to the model.".into()),
404 location: "Global".into(),
405 error_type: ErrorType::GlobalError,
406 positions: vec![],
407 });
408 }
409 }
410
411 fn validate_attribute(&mut self, attribute: &Attribute, types: &[&str], object: &Object) {
419 self.validate_attribute_name(&attribute.name, object);
420
421 let attribute_positions = extract_attribute_positions(object);
422
423 if attribute.dtypes.is_empty() {
424 self.add_error(ValidationError {
425 message: format!("Property '{}' has no type specified.", attribute.name),
426 object: Some(object.name.clone()),
427 attribute: Some(attribute.name.clone()),
428 location: "Global".into(),
429 error_type: ErrorType::TypeError,
430 solution: Some(format!(
431 "Add a type to the property '{}' using '- {}: <TYPE>'.",
432 attribute.name, attribute.name
433 )),
434 positions: attribute_positions
435 .get(&attribute.name)
436 .cloned()
437 .unwrap_or_default(),
438 })
439 }
440
441 for dtype in &attribute.dtypes {
442 self.check_attr_dtype(attribute, types, object, dtype);
443 }
444
445 if let Some(xml_option) = &attribute.xml {
446 match xml_option {
447 XMLType::Attribute { name, .. } => {
448 self.validate_xml_attribute_option(name, &object.name, &attribute.name);
449 }
450 XMLType::Element { name, .. } => {
451 self.validate_xml_element_option(name, &object.name, &attribute.name);
452 }
453 XMLType::Wrapped { name, wrapped, .. } => {
454 self.validate_xml_wrapped_option(name, &object.name, &attribute.name, wrapped);
455 }
456 }
457 }
458 }
459
460 fn check_attr_dtype(
469 &mut self,
470 attribute: &Attribute,
471 types: &[&str],
472 object: &Object,
473 dtype: &str,
474 ) {
475 let attribute_positions = extract_attribute_positions(object);
476
477 if dtype.is_empty() {
478 self.add_error(ValidationError {
479 message: format!(
480 "Property '{}' has no type defined. Either define a type or use a base type.",
481 attribute.name
482 ),
483 object: Some(object.name.clone()),
484 attribute: Some(attribute.name.clone()),
485 location: "Global".into(),
486 error_type: ErrorType::TypeError,
487 solution: Some(format!(
488 "Add a type to the property '{}' using '- {}: TYPE' after the property name.",
489 attribute.name, attribute.name
490 )),
491 positions: attribute_positions
492 .get(&attribute.name)
493 .cloned()
494 .unwrap_or_default(),
495 });
496
497 return;
498 }
499
500 if !types.contains(&dtype) && !BASIC_TYPES.contains(&dtype) {
501 self.add_error(ValidationError {
502 message: format!(
503 "Type '{}' of property '{}' not found.",
504 dtype, attribute.name
505 ),
506 object: Some(object.name.clone()),
507 attribute: Some(attribute.name.clone()),
508 location: "Global".into(),
509 error_type: ErrorType::TypeError,
510 solution: Some(format!(
511 "Add the type '{dtype}' to the model or use a base type."
512 )),
513 positions: attribute_positions
514 .get(&attribute.name)
515 .cloned()
516 .unwrap_or_default(),
517 })
518 }
519 }
520
521 fn validate_attribute_name(&mut self, name: &str, object: &Object) {
528 let checks = vec![starts_with_character, contains_white_space, |name: &str| {
529 contains_special_characters(name, false)
530 }];
531
532 let attribute_positions = extract_attribute_positions(object);
533
534 for check in checks {
535 if let Err((e, solution)) = check(name) {
536 self.add_error(ValidationError {
537 message: e,
538 object: Some(object.name.clone()),
539 attribute: Some(name.to_string()),
540 location: "Global".into(),
541 error_type: ErrorType::NameError,
542 solution: Some(format!("Resolve the issue by using '{solution}'.")),
543 positions: attribute_positions.get(name).cloned().unwrap_or_default(),
544 });
545 }
546 }
547 }
548
549 fn validate_xml_element_option(
559 &mut self,
560 option: &str,
561 object_name: &str,
562 attribute_name: &str,
563 ) {
564 let option = option.trim();
565 if option.is_empty() {
566 self.add_error(ValidationError {
567 message: "XML option is not defined.".into(),
568 object: Some(object_name.to_string()),
569 attribute: Some(attribute_name.to_string()),
570 location: "Global".into(),
571 error_type: ErrorType::XMLError,
572 solution: Some(format!(
573 "Add an XML option to the property '{attribute_name}' using '- XML: <TAG_NAME>' in a sub-list below the property name."
574 )),
575 positions: vec![],
576 });
577 }
578
579 let options = option.split(',').map(|s| s.trim()).collect::<Vec<_>>();
580 for opt in options {
581 if let Err((e, solution)) = contains_special_characters(opt.trim(), false) {
582 self.add_error(ValidationError {
583 message: e,
584 object: Some(object_name.to_string()),
585 attribute: Some(attribute_name.to_string()),
586 location: "Global".into(),
587 error_type: ErrorType::XMLError,
588 solution: Some(format!("Resolve the issue by using '{solution}'.")),
589 positions: vec![],
590 });
591 }
592 }
593 }
594
595 fn validate_xml_wrapped_option(
605 &mut self,
606 option: &str,
607 object_name: &str,
608 attribute_name: &str,
609 wrapped: &Option<Vec<String>>,
610 ) {
611 let option = option.trim();
612 if option.is_empty() {
613 self.add_error(ValidationError {
614 message: "XML option is not defined.".into(),
615 object: Some(object_name.to_string()),
616 attribute: Some(attribute_name.to_string()),
617 solution: Some(format!(
618 "Add an XML option to the property '{attribute_name}' using '- XML: <TAG_NAME>' in a sub-list below the property name."
619 )),
620 location: "Global".into(),
621 error_type: ErrorType::XMLError,
622 positions: vec![],
623 });
624 }
625
626 if let Some(wrapped_types) = wrapped {
627 if wrapped_types.len() > 2 {
628 self.add_error(ValidationError {
629 message: "XML wrapped option can only contain two types.".into(),
630 object: Some(object_name.to_string()),
631 attribute: Some(attribute_name.to_string()),
632 solution: Some("Reduce the depth of the wrapped option to two types and create a new object for the third type.".to_string()),
633 location: "Global".into(),
634 error_type: ErrorType::XMLError,
635 positions: vec![],
636 });
637 }
638
639 wrapped_types.iter().for_each(|wrapped_type| {
640 if let Err((e, solution)) = contains_special_characters(wrapped_type, true) {
641 self.add_error(ValidationError {
642 message: e,
643 object: Some(object_name.to_string()),
644 attribute: Some(attribute_name.to_string()),
645 solution: Some(format!("Resolve the issue by using '{solution}'.")),
646 location: "Global".into(),
647 error_type: ErrorType::XMLError,
648 positions: vec![],
649 });
650 }
651 });
652 }
653
654 let options = option.split(',').map(|s| s.trim()).collect::<Vec<_>>();
655 for opt in options {
656 if let Err((e, solution)) = contains_special_characters(opt.trim(), false) {
657 self.add_error(ValidationError {
658 message: e,
659 object: Some(object_name.to_string()),
660 attribute: Some(attribute_name.to_string()),
661 solution: Some(format!("Resolve the issue by using '{solution}'.")),
662 location: "Global".into(),
663 error_type: ErrorType::XMLError,
664 positions: vec![],
665 });
666 }
667 }
668 }
669
670 fn validate_xml_attribute_option(
688 &mut self,
689 option: &str,
690 object_name: &str,
691 attribute_name: &str,
692 ) {
693 let option = option.trim();
694 if option.is_empty() {
695 self.add_error(ValidationError {
696 message: "XML attribute option is not defined.".into(),
697 object: Some(object_name.to_string()),
698 attribute: Some(attribute_name.to_string()),
699 solution: Some(format!(
700 "Add an XML option to the property '{attribute_name}' using '- XML: @<ATTRIBUTE_NAME>' in a sub-list below the property name."
701 )),
702 location: "Global".into(),
703 error_type: ErrorType::XMLError,
704 positions: vec![],
705 });
706 }
707
708 let options = option.split(',').map(|s| s.trim()).collect::<Vec<_>>();
709 for opt in options {
710 if let Err((e, solution)) = contains_special_characters(opt, false) {
711 self.add_error(ValidationError {
712 message: e,
713 object: Some(object_name.to_string()),
714 attribute: Some(attribute_name.to_string()),
715 solution: Some(format!("Resolve the issue by using '{solution}'.")),
716 location: "Global".into(),
717 error_type: ErrorType::XMLError,
718 positions: vec![],
719 });
720 }
721 }
722 }
723
724 fn extract_type_names(model: &DataModel) -> Vec<&str> {
734 let types = model
735 .objects
736 .iter()
737 .map(|object| object.name.as_str())
738 .chain(model.enums.iter().map(|enum_| enum_.name.as_str()))
739 .collect::<Vec<&str>>();
740 types
741 }
742
743 fn sort_errors(&mut self) {
746 self.errors.sort_by(|a, b| {
747 let line_a = a.positions.first().map(|pos| pos.line);
748 let line_b = b.positions.first().map(|pos| pos.line);
749 line_a.cmp(&line_b)
750 });
751 }
752}
753
754impl Default for Validator {
755 fn default() -> Self {
757 Self::new()
758 }
759}
760
761fn unique_elements<T: Eq + std::hash::Hash + Clone>(input: &[T]) -> Vec<T> {
771 let mut set = HashSet::new();
772
773 for item in input {
774 set.insert(item.clone());
775 }
776
777 set.into_iter().collect()
778}
779
780fn get_duplicates<'a>(collection: &'a [&'a str]) -> Vec<&'a str> {
790 let mut seen = HashSet::new();
791 let mut duplicates = HashSet::new();
792
793 for &item in collection {
794 if !seen.insert(item) {
795 duplicates.insert(item);
796 }
797 }
798
799 duplicates.into_iter().collect()
800}
801
802fn starts_with_character(name: &str) -> Result<(), (String, String)> {
814 match name.chars().next() {
815 Some(c) if c.is_alphabetic() => Ok(()),
816 _ => Err((
817 format!("Name '{name}' must start with a letter."),
818 name[1..].to_string(),
819 )),
820 }
821}
822
823fn contains_white_space(name: &str) -> Result<(), (String, String)> {
835 if name.contains(' ') {
836 Err((
837 format!(
838 "Name '{name}' contains whitespace, which is not valid. Use underscores instead."
839 ),
840 name.replace(" ", "_").to_string(),
841 ))
842 } else {
843 Ok(())
844 }
845}
846
847fn contains_special_characters(name: &str, allow_slash: bool) -> Result<(), (String, String)> {
859 if name
860 .chars()
861 .any(|c| !c.is_alphanumeric() && c != '_' && c != ' ' && (!allow_slash || c != '/'))
862 {
863 Err((
864 format!("Name '{name}' contains special characters, which are not valid except for underscores."),
865 name.chars().filter(|c| c.is_alphanumeric() || *c == '_').collect::<String>().to_string(),
866 ))
867 } else {
868 Ok(())
869 }
870}
871
872fn extract_object_positions(model: &DataModel) -> HashMap<String, Vec<Position>> {
882 let mut positions: HashMap<String, Vec<Position>> = HashMap::new();
883 for object in &model.objects {
884 if object.position.is_none() {
885 continue;
886 }
887
888 if let Some(pos) = positions.get_mut(&object.name) {
889 pos.push(object.position.unwrap());
890 } else {
891 positions.insert(object.name.clone(), vec![object.position.unwrap()]);
892 }
893 }
894 positions
895}
896
897fn extract_enum_positions(model: &DataModel) -> HashMap<String, Vec<Position>> {
907 let mut positions: HashMap<String, Vec<Position>> = HashMap::new();
908 for enum_ in &model.enums {
909 if enum_.position.is_none() {
910 continue;
911 }
912
913 if let Some(pos) = positions.get_mut(&enum_.name) {
914 pos.push(enum_.position.unwrap());
915 } else {
916 positions.insert(enum_.name.clone(), vec![enum_.position.unwrap()]);
917 }
918 }
919 positions
920}
921
922fn extract_attribute_positions(object: &Object) -> HashMap<String, Vec<Position>> {
932 let mut positions: HashMap<String, Vec<Position>> = HashMap::new();
933 for attribute in &object.attributes {
934 if attribute.position.is_none() {
935 continue;
936 }
937
938 if let Some(pos) = positions.get_mut(&attribute.name) {
939 pos.push(attribute.position.unwrap());
940 } else {
941 positions.insert(attribute.name.clone(), vec![attribute.position.unwrap()]);
942 }
943 }
944 positions
945}
946
947fn get_line_numbers(positions: &[Position]) -> String {
957 positions
958 .iter()
959 .map(|p| p.line.to_string())
960 .collect::<Vec<String>>()
961 .join(", ")
962}