use edifact_rs::{
EdifactDeserialize, EdifactSerialize, EventEmitter, OwnedSegment, ProfileRulePack,
ValidationIssue, ValidationSeverity,
};
use crate::{
EdiEnergyMessage, EdiEnergyReport, Error, MessageType, Pruefidentifikator, Release,
messages::{
core::MessageCore,
segments::{Ucd, Uci, Ucm, Ucs, find_uci, try_deserialize},
},
};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ContrlMessageResponse {
pub ucm: Ucm,
pub segment_errors: Vec<ContrlSegmentError>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ContrlSegmentError {
pub ucs: Ucs,
pub element_errors: Vec<ContrlElementError>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ContrlElementError {
pub ucd: Ucd,
}
#[derive(Debug, Clone)]
pub struct ContrlMessage {
pub(crate) core: MessageCore,
uci: Option<Uci>,
message_responses: Vec<ContrlMessageResponse>,
}
impl ContrlMessage {
pub(crate) fn from_parts(
segments: Vec<OwnedSegment>,
message_ref: impl Into<Box<str>>,
assoc_code: impl Into<Box<str>>,
pruefidentifikator: Option<u32>,
) -> Self {
let (uci, message_responses) = {
let borrowed: Vec<edifact_rs::Segment<'_>> =
segments.iter().map(|s| s.as_borrowed()).collect();
let uci = find_uci(&borrowed);
let responses = parse_message_responses(&borrowed);
(uci, responses)
};
Self {
core: MessageCore::new(
segments,
message_ref,
assoc_code,
pruefidentifikator,
MessageType::Contrl,
),
uci,
message_responses,
}
}
#[must_use]
pub fn assoc_code(&self) -> &str {
&self.core.assoc_code
}
#[must_use]
pub fn segments(&self) -> &[OwnedSegment] {
&self.core.segments
}
#[must_use]
pub fn uci(&self) -> Option<&Uci> {
self.uci.as_ref()
}
#[must_use]
pub fn message_responses(&self) -> &[ContrlMessageResponse] {
&self.message_responses
}
}
impl EdifactDeserialize for ContrlMessage {
fn edifact_deserialize(
segments: &[edifact_rs::Segment<'_>],
) -> Result<Self, edifact_rs::EdifactError> {
let (message_ref, assoc_code) = MessageCore::extract_unh_fields(segments)?;
let owned: Vec<OwnedSegment> = segments.iter().cloned().map(OwnedSegment::from).collect();
Ok(Self::from_parts(owned, message_ref, assoc_code, None))
}
}
impl EdifactSerialize for ContrlMessage {
fn edifact_serialize<E: EventEmitter>(
&self,
emitter: &mut E,
) -> Result<(), edifact_rs::EdifactError> {
self.core.emit_segments(emitter)
}
}
impl EdiEnergyMessage for ContrlMessage {
fn try_message_type(&self) -> Option<MessageType> {
Some(self.core.message_type())
}
fn detect_release(&self) -> Result<&Release, Error> {
self.core.detect_release()
}
fn message_ref(&self) -> &str {
&self.core.message_ref
}
fn detect_pruefidentifikator(&self) -> Result<Pruefidentifikator, Error> {
Err(Error::MissingPruefidentifikator)
}
fn validate(&self) -> Result<EdiEnergyReport, Error> {
let release = self.core.detect_release()?;
self.core
.validate_against_with_semantic(release, Some(contrl_semantic_pack()))
}
fn validate_against(&self, release: &Release) -> Result<EdiEnergyReport, Error> {
self.core
.validate_against_with_semantic(release, Some(contrl_semantic_pack()))
}
fn serialize(&self) -> Result<Vec<u8>, Error> {
self.core.serialize()
}
fn segments(&self) -> &[edifact_rs::OwnedSegment] {
&self.core.segments
}
fn validate_with_pack(&self, extra: crate::CustomRulePack) -> Result<EdiEnergyReport, Error> {
self.core.validate_with_extra_pack(None, extra.into_inner())
}
fn validate_on_date(&self, reference_date: time::Date) -> Result<EdiEnergyReport, Error> {
let release = self.core.detect_release()?;
self.core
.validate_against_with_semantic_and_registry_on_date(
release,
Some(contrl_semantic_pack()),
crate::registry::ReleaseRegistry::global(),
Some(reference_date),
)
}
}
fn parse_message_responses(segments: &[edifact_rs::Segment<'_>]) -> Vec<ContrlMessageResponse> {
let mut result = Vec::new();
let mut i = 0;
while i < segments.len() {
if segments[i].tag != "UCM" {
i += 1;
continue;
}
let Some(ucm) = try_deserialize::<Ucm>(&segments[i]) else {
i += 1;
continue;
};
let mut segment_errors = Vec::new();
let mut j = i + 1;
while j < segments.len() && segments[j].tag != "UCM" && segments[j].tag != "UNT" {
if segments[j].tag != "UCS" {
j += 1;
continue;
}
let Some(ucs_seg) = try_deserialize::<Ucs>(&segments[j]) else {
j += 1;
continue;
};
let mut element_errors = Vec::new();
let mut k = j + 1;
while k < segments.len() && segments[k].tag == "UCD" {
if let Some(ucd) = try_deserialize::<Ucd>(&segments[k]) {
element_errors.push(ContrlElementError { ucd });
}
k += 1;
}
segment_errors.push(ContrlSegmentError {
ucs: ucs_seg,
element_errors,
});
j = k;
}
result.push(ContrlMessageResponse {
ucm,
segment_errors,
});
i = j;
}
result
}
const VALID_UCI_CODES: &[&str] = &["4", "7", "8"];
fn contrl_semantic_pack() -> ProfileRulePack {
ProfileRulePack::new("CONTRL-SEM")
.for_message_type("CONTRL")
.with_stateless_rule_fn(rule_sem_contrl_syntax_code)
}
fn rule_sem_contrl_syntax_code(
segments: &[edifact_rs::Segment<'_>],
issues: &mut Vec<ValidationIssue>,
) {
for seg in segments.iter().filter(|s| s.tag == "UCI") {
let code = seg.element_str(3).unwrap_or("");
if code.is_empty() {
continue;
}
if !VALID_UCI_CODES.contains(&code) {
issues.push(
ValidationIssue::new(
ValidationSeverity::Error,
format!(
"UCI acknowledgement code '{code}' is not in the UN/EDIFACT \
defined set (4=accepted, 7=rejected-group, 8=rejected-interchange)"
),
)
.with_span(seg.span)
.with_rule_id("SEM-CONTRL-SYNTAX-CODE-UNKNOWN")
.with_segment("UCI")
.with_suggestion(
"Valid UN/EDIFACT UCI acknowledgement codes (DE 0083): \
4 = interchange received, \
7 = rejected at functional-group level, \
8 = rejected at interchange level",
),
);
}
}
}