use automapper_validation::{
ConditionEvaluator, ConditionExprEvaluator, ConditionResult, EvaluationContext,
};
use mig_bo4e::pid_requirements::{CodeValue, EntityRequirement, PidRequirements};
use mig_bo4e::pid_validation::{PidValidationError, Severity};
use serde_json::Value;
pub fn validate_with_conditions<E: ConditionEvaluator>(
json: &Value,
requirements: &PidRequirements,
expr_eval: &ConditionExprEvaluator<'_, E>,
ctx: &EvaluationContext,
) -> Vec<PidValidationError> {
let mut errors = Vec::new();
for entity_req in &requirements.entities {
let key = to_camel_case(&entity_req.entity);
match json.get(&key) {
None => {
if let Some(severity) =
evaluate_ahb_requirement(&entity_req.ahb_status, expr_eval, ctx)
{
errors.push(PidValidationError::MissingEntity {
entity: entity_req.entity.clone(),
ahb_status: entity_req.ahb_status.clone(),
severity,
});
}
}
Some(val) => {
if entity_req.is_array {
if let Some(arr) = val.as_array() {
for element in arr {
validate_entity_fields_with_conditions(
element,
entity_req,
expr_eval,
ctx,
&mut errors,
);
}
}
} else {
validate_entity_fields_with_conditions(
val,
entity_req,
expr_eval,
ctx,
&mut errors,
);
}
}
}
}
errors
}
fn validate_entity_fields_with_conditions<E: ConditionEvaluator>(
entity_json: &Value,
entity_req: &EntityRequirement,
expr_eval: &ConditionExprEvaluator<'_, E>,
ctx: &EvaluationContext,
errors: &mut Vec<PidValidationError>,
) {
for field_req in &entity_req.fields {
match entity_json.get(&field_req.bo4e_name) {
None => {
if let Some(severity) =
evaluate_ahb_requirement(&field_req.ahb_status, expr_eval, ctx)
{
errors.push(PidValidationError::MissingField {
entity: entity_req.entity.clone(),
field: field_req.bo4e_name.clone(),
ahb_status: field_req.ahb_status.clone(),
rust_type: field_req.enum_name.clone(),
valid_values: code_values_to_tuples(&field_req.valid_codes),
severity,
});
}
}
Some(val) => {
if !field_req.valid_codes.is_empty() {
if let Some(value_str) = val.as_str() {
let is_valid = field_req.valid_codes.iter().any(|cv| cv.code == value_str);
if !is_valid {
errors.push(PidValidationError::InvalidCode {
entity: entity_req.entity.clone(),
field: field_req.bo4e_name.clone(),
value: value_str.to_string(),
valid_values: code_values_to_tuples(&field_req.valid_codes),
});
}
}
}
}
}
}
}
fn evaluate_ahb_requirement<E: ConditionEvaluator>(
ahb_status: &str,
expr_eval: &ConditionExprEvaluator<'_, E>,
ctx: &EvaluationContext,
) -> Option<Severity> {
match ahb_status {
"X" | "Muss" | "Soll" => Some(Severity::Error),
"Kann" | "" => None,
status => {
match expr_eval.evaluate_status(status, ctx) {
ConditionResult::True => Some(Severity::Error),
ConditionResult::False => None,
ConditionResult::Unknown => Some(Severity::Warning),
}
}
}
}
fn to_camel_case(s: &str) -> String {
if s.is_empty() {
return String::new();
}
let mut chars = s.chars();
let first = chars.next().unwrap();
let mut result = first.to_lowercase().to_string();
result.extend(chars);
result
}
fn code_values_to_tuples(codes: &[CodeValue]) -> Vec<(String, String)> {
codes
.iter()
.map(|cv| (cv.code.clone(), cv.meaning.clone()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_camel_case() {
assert_eq!(to_camel_case("Prozessdaten"), "prozessdaten");
assert_eq!(
to_camel_case("RuhendeMarktlokation"),
"ruhendeMarktlokation"
);
assert_eq!(to_camel_case(""), "");
}
}