use std::collections::{HashMap, HashSet};
use crate::{mapper_entry::MapperEntry, struct_entry::StructEntry};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum FieldError {
DupField(String),
MissingField(String),
}
#[derive(Debug)]
#[allow(dead_code)]
pub enum ValidationError {
MapperEntryError(Vec<FieldError>),
StructEntryError(Vec<FieldError>),
DtoNameDuplicated(Vec<String>),
MissingPropertyError(String),
}
pub fn validate_entry_data(
st_entry: &StructEntry,
mp_entries: &[MapperEntry],
) -> Result<(), ValidationError> {
validate_mapper_entries(mp_entries)?;
validate_struct_entry(st_entry, mp_entries)?;
validate_dto_name(mp_entries)?;
validate_map_ignore(mp_entries)?;
Ok(())
}
fn validate_map_ignore(mp_entries: &[MapperEntry]) -> Result<(), ValidationError> {
let invalid_entries: Vec<String> = mp_entries
.iter()
.filter(|entry| entry.map.is_empty() && entry.ignore.is_empty() && !entry.exactly)
.map(|entry| entry.dto.clone())
.collect();
if !invalid_entries.is_empty() {
return Err(ValidationError::MissingPropertyError(format!(
"mapper requires a `map` or an `ignore` property for DTOs: {:?}",
invalid_entries
)));
}
Ok(())
}
fn validate_dto_name(mp_entries: &[MapperEntry]) -> Result<(), ValidationError> {
let mut dto_counts = HashMap::new();
for entry in mp_entries {
*dto_counts.entry(entry.dto.clone()).or_insert(0) += 1;
}
let duplicates: Vec<String> = dto_counts
.into_iter()
.filter(|(_, count)| *count > 1)
.map(|(name, _)| name)
.collect();
if !duplicates.is_empty() {
return Err(ValidationError::DtoNameDuplicated(duplicates));
}
Ok(())
}
fn validate_struct_entry(
st_entry: &StructEntry,
mp_entries: &[MapperEntry],
) -> Result<(), ValidationError> {
let valid_fields: HashSet<&String> = st_entry
.field_entries
.iter()
.map(|f| &f.field_name)
.collect();
let mut errors = Vec::new();
for entry in mp_entries {
let missing_fields: Vec<String> = entry
.map
.iter()
.filter(|value| !valid_fields.contains(&value.from_field))
.map(|value| value.from_field.clone())
.collect();
if !missing_fields.is_empty() {
errors.push(FieldError::MissingField(format!(
"{} field name doesn't exist in structure={}. List of wrong map field names: {:?}",
entry.dto, st_entry.name, missing_fields
)));
}
}
if !errors.is_empty() {
return Err(ValidationError::StructEntryError(errors));
}
Ok(())
}
fn validate_mapper_entries(mp_entries: &[MapperEntry]) -> Result<(), ValidationError> {
let mut errors = Vec::new();
for entry in mp_entries {
validate_single_mapper_entry(entry, &mut errors);
}
if !errors.is_empty() {
return Err(ValidationError::MapperEntryError(errors));
}
Ok(())
}
fn validate_single_mapper_entry(entry: &MapperEntry, errors: &mut Vec<FieldError>) {
let mut source_counts = HashMap::new();
let mut dest_counts = HashMap::new();
for map_value in &entry.map {
*source_counts.entry(&map_value.from_field).or_insert(0) += 1;
if let Some(ref to_field) = map_value.to_field {
*dest_counts.entry(to_field).or_insert(0) += 1;
}
}
check_overlapping_fields(entry, &source_counts, &dest_counts, errors);
check_duplicate_source_fields(entry, &source_counts, errors);
check_duplicate_dest_fields(entry, &dest_counts, errors);
}
fn check_overlapping_fields(
entry: &MapperEntry,
source_counts: &HashMap<&String, u8>,
dest_counts: &HashMap<&String, u8>,
errors: &mut Vec<FieldError>,
) {
let overlapping_fields: Vec<String> = dest_counts
.keys()
.filter(|key| source_counts.contains_key(*key))
.map(|&key| key.clone())
.collect();
if !overlapping_fields.is_empty() {
errors.push(FieldError::DupField(format!(
"duplicate mapping destination keys found in dto={} entry: {:?}",
entry.dto, overlapping_fields
)));
}
}
fn get_duplicate_fields<'a>(field_counts: &HashMap<&'a String, u8>) -> Vec<String> {
field_counts
.iter()
.filter(|(_, &count)| count > 1)
.map(|(&field, _)| field.clone())
.collect()
}
fn check_duplicate_source_fields(
entry: &MapperEntry,
source_counts: &HashMap<&String, u8>,
errors: &mut Vec<FieldError>,
) {
let duplicate_sources = get_duplicate_fields(source_counts);
if !duplicate_sources.is_empty() {
errors.push(FieldError::DupField(format!(
"duplicate source key names found in dto={} entry: {:?}",
entry.dto, duplicate_sources
)));
}
}
fn check_duplicate_dest_fields(
entry: &MapperEntry,
dest_counts: &HashMap<&String, u8>,
errors: &mut Vec<FieldError>,
) {
let duplicate_destinations = get_duplicate_fields(dest_counts);
if !duplicate_destinations.is_empty() {
errors.push(FieldError::DupField(format!(
"duplicate destination key names found in dto={} entry: {:?}",
entry.dto, duplicate_destinations
)));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mapper_entry::MapValue;
use crate::struct_entry::FieldEntry;
use proc_macro2::TokenStream;
use std::str::FromStr;
use syn::Type;
fn create_test_struct_entry(fields: Vec<&str>) -> StructEntry {
let field_entries = fields
.into_iter()
.map(|name| FieldEntry {
field_name: name.to_string(),
field_type: Type::Verbatim(TokenStream::from_str("String").unwrap()),
is_optional: false,
})
.collect();
StructEntry {
name: "TestStruct".to_string(),
field_entries,
}
}
fn create_test_mapper_entry(
dto_name: &str,
mappings: Vec<(&str, Option<&str>, bool)>,
) -> MapperEntry {
let map = mappings
.into_iter()
.map(|(from, to, required)| MapValue {
from_field: from.to_string(),
to_field: to.map(|s| s.to_string()),
required,
macro_attr: vec![],
})
.collect();
MapperEntry {
dto: dto_name.to_string(),
map,
ignore: vec![],
derive: vec![],
no_builder: false,
new_fields: vec![],
exactly: false,
macro_attr: vec![],
}
}
#[test]
fn test_validate_dto_name_success() {
let entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto2", vec![("field2", None, true)]),
];
let result = validate_dto_name(&entries);
assert!(result.is_ok());
}
#[test]
fn test_validate_dto_name_failure() {
let entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto1", vec![("field2", None, true)]),
];
let result = validate_dto_name(&entries);
assert!(result.is_err());
if let Err(ValidationError::DtoNameDuplicated(dups)) = result {
assert_eq!(dups, vec!["Dto1"]);
} else {
panic!("Expected DtoNameDuplicated error");
}
}
#[test]
fn test_validate_struct_entry_success() {
let struct_entry = create_test_struct_entry(vec!["field1", "field2"]);
let mapper_entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto2", vec![("field2", None, true)]),
];
let result = validate_struct_entry(&struct_entry, &mapper_entries);
assert!(result.is_ok());
}
#[test]
fn test_validate_struct_entry_failure() {
let struct_entry = create_test_struct_entry(vec!["field1", "field2"]);
let mapper_entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto2", vec![("field3", None, true)]), ];
let result = validate_struct_entry(&struct_entry, &mapper_entries);
assert!(result.is_err());
if let Err(ValidationError::StructEntryError(errors)) = result {
assert_eq!(errors.len(), 1);
if let FieldError::MissingField(msg) = &errors[0] {
assert!(msg.contains("field3"));
assert!(msg.contains("Dto2"));
assert!(msg.contains("TestStruct"));
} else {
panic!("Expected MissingField error");
}
} else {
panic!("Expected StructEntryError");
}
}
#[test]
fn test_validate_map_ignore_success() {
let entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto2", vec![]),
];
let mut entries = entries;
entries[1].exactly = true;
let result = validate_map_ignore(&entries);
assert!(result.is_ok());
}
#[test]
fn test_validate_map_ignore_failure() {
let mut entry = create_test_mapper_entry("EmptyDto", vec![]);
entry.exactly = false;
let result = validate_map_ignore(&[entry]);
assert!(result.is_err());
if let Err(ValidationError::MissingPropertyError(msg)) = result {
assert!(msg.contains("EmptyDto"));
} else {
panic!("Expected MissingPropertyError");
}
}
#[test]
fn test_validate_mapper_entries_success() {
let entries = vec![create_test_mapper_entry(
"Dto1",
vec![("field1", None, true), ("field2", Some("renamed"), true)],
)];
let result = validate_mapper_entries(&entries);
assert!(result.is_ok());
}
#[test]
fn test_validate_mapper_entries_failure_duplicate_source() {
let entries = vec![create_test_mapper_entry(
"Dto1",
vec![
("field1", None, true),
("field1", Some("renamed"), true), ],
)];
let result = validate_mapper_entries(&entries);
assert!(result.is_err());
if let Err(ValidationError::MapperEntryError(errors)) = result {
assert_eq!(errors.len(), 1);
if let FieldError::DupField(msg) = &errors[0] {
assert!(msg.contains("duplicate source"));
assert!(msg.contains("field1"));
} else {
panic!("Expected DupField error");
}
} else {
panic!("Expected MapperEntryError");
}
}
#[test]
fn test_validate_mapper_entries_failure_duplicate_dest() {
let entries = vec![create_test_mapper_entry(
"Dto1",
vec![
("field1", Some("same_dest"), true),
("field2", Some("same_dest"), true), ],
)];
let result = validate_mapper_entries(&entries);
assert!(result.is_err());
if let Err(ValidationError::MapperEntryError(errors)) = result {
assert_eq!(errors.len(), 1);
if let FieldError::DupField(msg) = &errors[0] {
assert!(msg.contains("duplicate destination"));
assert!(msg.contains("same_dest"));
} else {
panic!("Expected DupField error");
}
} else {
panic!("Expected MapperEntryError");
}
}
#[test]
fn test_validate_mapper_entries_failure_overlapping_fields() {
let entries = vec![create_test_mapper_entry(
"Dto1",
vec![
("overlap_field", None, true),
("field2", Some("overlap_field"), true), ],
)];
let result = validate_mapper_entries(&entries);
assert!(result.is_err());
if let Err(ValidationError::MapperEntryError(errors)) = result {
assert_eq!(errors.len(), 1);
if let FieldError::DupField(msg) = &errors[0] {
assert!(msg.contains("duplicate mapping destination"));
assert!(msg.contains("overlap_field"));
} else {
panic!("Expected DupField error");
}
} else {
panic!("Expected MapperEntryError");
}
}
#[test]
fn test_validate_entry_data_success() {
let struct_entry = create_test_struct_entry(vec!["field1", "field2"]);
let mapper_entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto2", vec![("field2", None, true)]),
];
let result = validate_entry_data(&struct_entry, &mapper_entries);
assert!(result.is_ok());
}
#[test]
fn test_validate_entry_data_failure() {
let struct_entry = create_test_struct_entry(vec!["field1", "field2"]);
let mapper_entries = vec![
create_test_mapper_entry("Dto1", vec![("field1", None, true)]),
create_test_mapper_entry("Dto1", vec![("field2", None, true)]), ];
let result = validate_entry_data(&struct_entry, &mapper_entries);
assert!(result.is_err());
if let Err(ValidationError::DtoNameDuplicated(_)) = result {
} else {
panic!("Expected DtoNameDuplicated error");
}
}
}