Skip to main content

eric_sdk/
validation.rs

1use crate::EricError;
2use anyhow::anyhow;
3use quick_xml::de::from_str;
4use serde::Deserialize;
5
6/// One `<FehlerRegelpruefung>` entry from ERiC validation XML.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ValidationIssue {
9    pub data_ticket: Option<String>,
10    pub field_identifier: Option<String>,
11    pub multi_line_index: Option<u32>,
12    pub form_sequence_number: Option<u32>,
13    pub rule_name: Option<String>,
14    pub error_code: Option<String>,
15    pub text: Option<String>,
16}
17
18/// Parsed summary of ERiC validation XML.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct ValidationReport {
21    /// Structured validation issues extracted from the XML.
22    pub issues: Vec<ValidationIssue>,
23}
24
25impl ValidationReport {
26    pub fn parse(xml: &str) -> Result<Self, EricError> {
27        let xml_report = from_str::<XmlValidationReport>(xml).map_err(|err| anyhow!("{err}"))?;
28
29        let issues = xml_report
30            .issues
31            .into_iter()
32            .map(|issue| ValidationIssue {
33                data_ticket: issue.data_ticket,
34                field_identifier: issue.field_identifier,
35                multi_line_index: issue.multi_line_index,
36                form_sequence_number: issue.form_sequence_number,
37                rule_name: issue.rule_name,
38                error_code: issue.error_code,
39                text: issue.text,
40            })
41            .collect();
42
43        Ok(Self { issues })
44    }
45}
46
47/// The raw xml response from ERiC may contain multiple `<FehlerRegelpruefung>`
48/// entries, each describing a validation issue.
49#[derive(Debug, Deserialize)]
50struct XmlValidationReport {
51    #[serde(rename = "FehlerRegelpruefung", default)]
52    issues: Vec<XmlValidationIssue>,
53}
54
55/// An individual `<FehlerRegelpruefung>` entry from the ERiC validation XML.
56#[derive(Debug, Deserialize)]
57struct XmlValidationIssue {
58    #[serde(rename = "Nutzdatenticket")]
59    data_ticket: Option<String>,
60    #[serde(rename = "Feldidentifikator")]
61    field_identifier: Option<String>,
62    #[serde(rename = "Mehrfachzeilenindex")]
63    multi_line_index: Option<u32>,
64    #[serde(rename = "LfdNrVordruck")]
65    form_sequence_number: Option<u32>,
66    #[serde(rename = "RegelName")]
67    rule_name: Option<String>,
68    #[serde(rename = "FachlicheFehlerId")]
69    error_code: Option<String>,
70    #[serde(rename = "Text")]
71    text: Option<String>,
72}
73
74#[cfg(test)]
75mod tests {
76    use crate::{EricApiPayload, EricError};
77
78    #[test]
79    fn parse_validation_report_from_api_error() {
80        let err = EricError::ApiError {
81            code: 610001002,
82            message: "Fehler waehrend der Plausibilitaetspruefung".to_string(),
83            payload: EricApiPayload::new(
84                r#"<?xml version="1.0" encoding="UTF-8"?>
85<EricBearbeiteVorgang xmlns="http://www.elster.de/EricXML/1.1/EricBearbeiteVorgang">
86  <FehlerRegelpruefung>
87    <Nutzdatenticket>-</Nutzdatenticket>
88        <Feldidentifikator>gcd:genInfo.report.id.accountingStandard</Feldidentifikator>
89    <Mehrfachzeilenindex>1</Mehrfachzeilenindex>
90    <LfdNrVordruck>1</LfdNrVordruck>
91    <FachlicheFehlerId>170105000</FachlicheFehlerId>
92    <Text>missing required attribute 'unitRef'</Text>
93  </FehlerRegelpruefung>
94</EricBearbeiteVorgang>"#
95                    .to_string(),
96                String::new(),
97            ),
98        };
99
100        let report = err
101            .validation_report()
102            .expect("report should parse")
103            .unwrap();
104        assert_eq!(report.issues.len(), 1);
105
106        let issue = &report.issues[0];
107        assert_eq!(
108            issue.field_identifier.as_deref(),
109            Some("gcd:genInfo.report.id.accountingStandard")
110        );
111        assert_eq!(issue.multi_line_index, Some(1));
112        assert_eq!(issue.form_sequence_number, Some(1));
113        assert_eq!(issue.rule_name, None);
114        assert_eq!(issue.error_code.as_deref(), Some("170105000"));
115        assert!(issue
116            .text
117            .as_deref()
118            .unwrap_or_default()
119            .contains("unitRef"));
120    }
121}