edifact_mapper/
conditional_validation.rs1use automapper_validation::{
8 ConditionEvaluator, ConditionExprEvaluator, ConditionResult, EvaluationContext,
9};
10use mig_bo4e::pid_requirements::{CodeValue, EntityRequirement, PidRequirements};
11use mig_bo4e::pid_validation::{PidValidationError, Severity};
12use serde_json::Value;
13
14pub fn validate_with_conditions<E: ConditionEvaluator>(
24 json: &Value,
25 requirements: &PidRequirements,
26 expr_eval: &ConditionExprEvaluator<'_, E>,
27 ctx: &EvaluationContext,
28) -> Vec<PidValidationError> {
29 let mut errors = Vec::new();
30
31 for entity_req in &requirements.entities {
32 let key = to_camel_case(&entity_req.entity);
33
34 match json.get(&key) {
35 None => {
36 if let Some(severity) =
37 evaluate_ahb_requirement(&entity_req.ahb_status, expr_eval, ctx)
38 {
39 errors.push(PidValidationError::MissingEntity {
40 entity: entity_req.entity.clone(),
41 ahb_status: entity_req.ahb_status.clone(),
42 severity,
43 });
44 }
45 }
46 Some(val) => {
47 if entity_req.is_array {
48 if let Some(arr) = val.as_array() {
49 for element in arr {
50 validate_entity_fields_with_conditions(
51 element,
52 entity_req,
53 expr_eval,
54 ctx,
55 &mut errors,
56 );
57 }
58 }
59 } else {
60 validate_entity_fields_with_conditions(
61 val,
62 entity_req,
63 expr_eval,
64 ctx,
65 &mut errors,
66 );
67 }
68 }
69 }
70 }
71
72 errors
73}
74
75fn validate_entity_fields_with_conditions<E: ConditionEvaluator>(
77 entity_json: &Value,
78 entity_req: &EntityRequirement,
79 expr_eval: &ConditionExprEvaluator<'_, E>,
80 ctx: &EvaluationContext,
81 errors: &mut Vec<PidValidationError>,
82) {
83 for field_req in &entity_req.fields {
84 match entity_json.get(&field_req.bo4e_name) {
85 None => {
86 if let Some(severity) =
87 evaluate_ahb_requirement(&field_req.ahb_status, expr_eval, ctx)
88 {
89 errors.push(PidValidationError::MissingField {
90 entity: entity_req.entity.clone(),
91 field: field_req.bo4e_name.clone(),
92 ahb_status: field_req.ahb_status.clone(),
93 rust_type: field_req.enum_name.clone(),
94 valid_values: code_values_to_tuples(&field_req.valid_codes),
95 severity,
96 });
97 }
98 }
99 Some(val) => {
100 if !field_req.valid_codes.is_empty() {
103 if let Some(value_str) = val.as_str() {
104 let is_valid = field_req.valid_codes.iter().any(|cv| cv.code == value_str);
105 if !is_valid {
106 errors.push(PidValidationError::InvalidCode {
107 entity: entity_req.entity.clone(),
108 field: field_req.bo4e_name.clone(),
109 value: value_str.to_string(),
110 valid_values: code_values_to_tuples(&field_req.valid_codes),
111 });
112 }
113 }
114 }
115 }
116 }
117 }
118}
119
120fn evaluate_ahb_requirement<E: ConditionEvaluator>(
127 ahb_status: &str,
128 expr_eval: &ConditionExprEvaluator<'_, E>,
129 ctx: &EvaluationContext,
130) -> Option<Severity> {
131 match ahb_status {
132 "X" | "Muss" | "Soll" => Some(Severity::Error),
133 "Kann" | "" => None,
134 status => {
135 match expr_eval.evaluate_status(status, ctx) {
138 ConditionResult::True => Some(Severity::Error),
139 ConditionResult::False => None,
140 ConditionResult::Unknown => Some(Severity::Warning),
141 }
142 }
143 }
144}
145
146fn to_camel_case(s: &str) -> String {
148 if s.is_empty() {
149 return String::new();
150 }
151 let mut chars = s.chars();
152 let first = chars.next().unwrap();
153 let mut result = first.to_lowercase().to_string();
154 result.extend(chars);
155 result
156}
157
158fn code_values_to_tuples(codes: &[CodeValue]) -> Vec<(String, String)> {
160 codes
161 .iter()
162 .map(|cv| (cv.code.clone(), cv.meaning.clone()))
163 .collect()
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_to_camel_case() {
172 assert_eq!(to_camel_case("Prozessdaten"), "prozessdaten");
173 assert_eq!(
174 to_camel_case("RuhendeMarktlokation"),
175 "ruhendeMarktlokation"
176 );
177 assert_eq!(to_camel_case(""), "");
178 }
179}