use automapper_validation::{
AbsentTarget, ConditionEvaluator, ConditionExprEvaluator, ConditionResult, EvaluationContext,
};
use mig_bo4e::pid_requirements::{CodeValue, EntityRequirement, PidRequirements};
use mig_bo4e::pid_validation::{
effective_field_requirements, fields_of_unrequired_absent_groups, get_nested,
invalid_code_value, 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 absent = AbsentTarget(expr_eval.evaluator());
let expr_eval = &ConditionExprEvaluator::new(&absent);
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.cardinality().is_list() {
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,
);
}
} 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>,
) {
let fields = effective_field_requirements(entity_req, entity_json);
let exempt = fields_of_unrequired_absent_groups(entity_req, entity_json, |status| {
evaluate_ahb_requirement(status, expr_eval, ctx) == Some(Severity::Error)
});
for field_req in fields.iter() {
match get_nested(entity_json, &field_req.bo4e_name).filter(|v| !v.is_null()) {
None if exempt.contains(field_req.bo4e_name.as_str()) => {}
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 let Some(value) = invalid_code_value(val, field_req) {
errors.push(PidValidationError::InvalidCode {
entity: entity_req.entity.clone(),
field: field_req.bo4e_name.clone(),
value,
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.trim() {
"X" | "Muss" | "Soll" => Some(Severity::Error),
"Kann" | "K" | "" => None,
status if status.starts_with("Kann") || status.starts_with("K ") => 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 optional_statuses_are_never_required() {
use automapper_validation::MapExternalProvider;
struct NoConditions;
impl ConditionEvaluator for NoConditions {
fn evaluate(&self, _: u32, _: &EvaluationContext) -> ConditionResult {
ConditionResult::True
}
fn is_external(&self, _: u32) -> bool {
false
}
fn message_type(&self) -> &str {
"UTILMD"
}
fn format_version(&self) -> &str {
"FV2604"
}
}
let evaluator = NoConditions;
let expr_eval = ConditionExprEvaluator::new(&evaluator);
let external = MapExternalProvider::new(Default::default());
let ctx = EvaluationContext::new("55042", &external, &[]);
for status in ["K", "Kann", "", "Kann [1]", "K [1]"] {
assert_eq!(
evaluate_ahb_requirement(status, &expr_eval, &ctx),
None,
"{status:?} must be optional"
);
}
assert_eq!(
evaluate_ahb_requirement("X", &expr_eval, &ctx),
Some(Severity::Error)
);
}
#[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(""), "");
}
}