1use edifact_rs::{
2 EdifactDeserialize, EdifactSerialize, EventEmitter, OwnedSegment, ProfileRulePack,
3 ValidationIssue, ValidationSeverity,
4};
5
6use crate::{
7 EdiEnergyMessage, EdiEnergyReport, Error, MessageType, Pruefidentifikator, Release,
8 messages::{
9 core::MessageCore,
10 segments::{Ucd, Uci, Ucm, Ucs, find_uci, try_deserialize},
11 },
12};
13
14#[derive(Debug, Clone)]
21#[non_exhaustive]
22pub struct ContrlMessageResponse {
23 pub ucm: Ucm,
25 pub segment_errors: Vec<ContrlSegmentError>,
27}
28
29#[derive(Debug, Clone)]
31#[non_exhaustive]
32pub struct ContrlSegmentError {
33 pub ucs: Ucs,
35 pub element_errors: Vec<ContrlElementError>,
37}
38
39#[derive(Debug, Clone)]
41#[non_exhaustive]
42pub struct ContrlElementError {
43 pub ucd: Ucd,
45}
46
47#[derive(Debug, Clone)]
64pub struct ContrlMessage {
65 pub(crate) core: MessageCore,
66 uci: Option<Uci>,
68 message_responses: Vec<ContrlMessageResponse>,
72}
73
74impl ContrlMessage {
75 pub(crate) fn from_parts(
76 segments: Vec<OwnedSegment>,
77 message_ref: impl Into<Box<str>>,
78 assoc_code: impl Into<Box<str>>,
79 pruefidentifikator: Option<u32>,
80 ) -> Self {
81 let (uci, message_responses) = {
82 let borrowed: Vec<edifact_rs::Segment<'_>> =
83 segments.iter().map(|s| s.as_borrowed()).collect();
84 let uci = find_uci(&borrowed);
85 let responses = parse_message_responses(&borrowed);
86 (uci, responses)
87 };
88 Self {
89 core: MessageCore::new(
90 segments,
91 message_ref,
92 assoc_code,
93 pruefidentifikator,
94 MessageType::Contrl,
95 ),
96 uci,
97 message_responses,
98 }
99 }
100
101 #[must_use]
103 pub fn assoc_code(&self) -> &str {
104 &self.core.assoc_code
105 }
106
107 #[must_use]
109 pub fn segments(&self) -> &[OwnedSegment] {
110 &self.core.segments
111 }
112
113 #[must_use]
115 pub fn uci(&self) -> Option<&Uci> {
116 self.uci.as_ref()
117 }
118
119 #[must_use]
121 pub fn message_responses(&self) -> &[ContrlMessageResponse] {
122 &self.message_responses
123 }
124}
125
126impl EdifactDeserialize for ContrlMessage {
129 fn edifact_deserialize(
130 segments: &[edifact_rs::Segment<'_>],
131 ) -> Result<Self, edifact_rs::EdifactError> {
132 let (message_ref, assoc_code) = MessageCore::extract_unh_fields(segments)?;
133 let owned: Vec<OwnedSegment> = segments.iter().cloned().map(OwnedSegment::from).collect();
134 Ok(Self::from_parts(owned, message_ref, assoc_code, None))
135 }
136}
137
138impl EdifactSerialize for ContrlMessage {
141 fn edifact_serialize<E: EventEmitter>(
142 &self,
143 emitter: &mut E,
144 ) -> Result<(), edifact_rs::EdifactError> {
145 self.core.emit_segments(emitter)
146 }
147}
148impl EdiEnergyMessage for ContrlMessage {
149 fn try_message_type(&self) -> Option<MessageType> {
150 Some(self.core.message_type())
151 }
152 fn detect_release(&self) -> Result<&Release, Error> {
153 self.core.detect_release()
154 }
155 fn message_ref(&self) -> &str {
156 &self.core.message_ref
157 }
158 fn detect_pruefidentifikator(&self) -> Result<Pruefidentifikator, Error> {
163 Err(Error::MissingPruefidentifikator)
164 }
165 fn validate(&self) -> Result<EdiEnergyReport, Error> {
166 let release = self.core.detect_release()?;
167 self.core
168 .validate_against_with_semantic(release, Some(contrl_semantic_pack()))
169 }
170 fn validate_against(&self, release: &Release) -> Result<EdiEnergyReport, Error> {
171 self.core
172 .validate_against_with_semantic(release, Some(contrl_semantic_pack()))
173 }
174 fn serialize(&self) -> Result<Vec<u8>, Error> {
175 self.core.serialize()
176 }
177 fn segments(&self) -> &[edifact_rs::OwnedSegment] {
178 &self.core.segments
179 }
180 fn validate_with_pack(&self, extra: crate::CustomRulePack) -> Result<EdiEnergyReport, Error> {
181 self.core.validate_with_extra_pack(None, extra.into_inner())
182 }
183 fn validate_on_date(&self, reference_date: time::Date) -> Result<EdiEnergyReport, Error> {
184 let release = self.core.detect_release()?;
185 self.core
186 .validate_against_with_semantic_and_registry_on_date(
187 release,
188 Some(contrl_semantic_pack()),
189 crate::registry::ReleaseRegistry::global(),
190 Some(reference_date),
191 )
192 }
193}
194
195fn parse_message_responses(segments: &[edifact_rs::Segment<'_>]) -> Vec<ContrlMessageResponse> {
203 let mut result = Vec::new();
204 let mut i = 0;
205
206 while i < segments.len() {
207 if segments[i].tag != "UCM" {
208 i += 1;
209 continue;
210 }
211 let Some(ucm) = try_deserialize::<Ucm>(&segments[i]) else {
212 i += 1;
213 continue;
214 };
215
216 let mut segment_errors = Vec::new();
218 let mut j = i + 1;
219 while j < segments.len() && segments[j].tag != "UCM" && segments[j].tag != "UNT" {
220 if segments[j].tag != "UCS" {
221 j += 1;
222 continue;
223 }
224 let Some(ucs_seg) = try_deserialize::<Ucs>(&segments[j]) else {
225 j += 1;
226 continue;
227 };
228
229 let mut element_errors = Vec::new();
231 let mut k = j + 1;
232 while k < segments.len() && segments[k].tag == "UCD" {
233 if let Some(ucd) = try_deserialize::<Ucd>(&segments[k]) {
234 element_errors.push(ContrlElementError { ucd });
235 }
236 k += 1;
237 }
238 segment_errors.push(ContrlSegmentError {
239 ucs: ucs_seg,
240 element_errors,
241 });
242 j = k;
243 }
244
245 result.push(ContrlMessageResponse {
246 ucm,
247 segment_errors,
248 });
249 i = j;
250 }
251
252 result
253}
254
255const VALID_UCI_CODES: &[&str] = &["4", "7", "8"];
263
264fn contrl_semantic_pack() -> ProfileRulePack {
270 ProfileRulePack::new("CONTRL-SEM")
271 .for_message_type("CONTRL")
272 .with_stateless_rule_fn(rule_sem_contrl_syntax_code)
273}
274
275fn rule_sem_contrl_syntax_code(
285 segments: &[edifact_rs::Segment<'_>],
286 issues: &mut Vec<ValidationIssue>,
287) {
288 for seg in segments.iter().filter(|s| s.tag == "UCI") {
289 let code = seg.element_str(3).unwrap_or("");
291 if code.is_empty() {
292 continue;
293 }
294 if !VALID_UCI_CODES.contains(&code) {
295 issues.push(
296 ValidationIssue::new(
297 ValidationSeverity::Error,
298 format!(
299 "UCI acknowledgement code '{code}' is not in the UN/EDIFACT \
300 defined set (4=accepted, 7=rejected-group, 8=rejected-interchange)"
301 ),
302 )
303 .with_span(seg.span)
304 .with_rule_id("SEM-CONTRL-SYNTAX-CODE-UNKNOWN")
305 .with_segment("UCI")
306 .with_suggestion(
307 "Valid UN/EDIFACT UCI acknowledgement codes (DE 0083): \
308 4 = interchange received, \
309 7 = rejected at functional-group level, \
310 8 = rejected at interchange level",
311 ),
312 );
313 }
314 }
315}