use edifact_rs::{
EdifactDeserialize, EdifactSerialize, EventEmitter, OwnedSegment, ProfileRulePack,
ValidationIssue, ValidationSeverity,
};
use crate::{
MessageType,
messages::{
core::MessageCore,
segments::{
Bgm, Dtm, Erc, Ftx, Nad, Rff, collect_dtm, find_bgm, find_nad, find_rff,
try_deserialize,
},
},
};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct AperakError {
pub erc: Erc,
pub ftx: Vec<Ftx>,
pub references: Vec<Rff>,
}
#[derive(Debug, Clone)]
pub struct AperakMessage {
pub(crate) core: MessageCore,
bgm: Option<Bgm>,
dtm: Vec<Dtm>,
sender: Option<Nad>,
receiver: Option<Nad>,
ref_acw: Option<Rff>,
errors: Vec<AperakError>,
}
impl AperakMessage {
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 (bgm, dtm, sender, receiver, ref_acw, errors) = {
let borrowed: Vec<edifact_rs::Segment<'_>> =
segments.iter().map(|s| s.as_borrowed()).collect();
(
find_bgm(&borrowed),
collect_dtm(&borrowed),
find_nad(&borrowed, "MS"),
find_nad(&borrowed, "MR"),
find_rff(&borrowed, "ACW"),
parse_errors(&borrowed),
)
};
Self {
core: MessageCore::new(
segments,
message_ref,
assoc_code,
pruefidentifikator,
MessageType::Aperak,
),
bgm,
dtm,
sender,
receiver,
ref_acw,
errors,
}
}
#[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 bgm(&self) -> Option<&Bgm> {
self.bgm.as_ref()
}
#[must_use]
pub fn dtm(&self) -> &[Dtm] {
&self.dtm
}
#[must_use]
pub fn sender(&self) -> Option<&Nad> {
self.sender.as_ref()
}
#[must_use]
pub fn receiver(&self) -> Option<&Nad> {
self.receiver.as_ref()
}
#[must_use]
pub fn ref_acw(&self) -> Option<&Rff> {
self.ref_acw.as_ref()
}
#[must_use]
pub fn errors(&self) -> &[AperakError] {
&self.errors
}
}
impl EdifactDeserialize for AperakMessage {
fn edifact_deserialize(
segments: &[edifact_rs::Segment<'_>],
) -> Result<Self, edifact_rs::EdifactError> {
let (message_ref, assoc_code) = MessageCore::extract_unh_fields(segments)?;
let pid = MessageCore::extract_bgm_pid(segments);
let owned: Vec<OwnedSegment> = segments.iter().cloned().map(OwnedSegment::from).collect();
Ok(Self::from_parts(owned, message_ref, assoc_code, pid))
}
}
impl EdifactSerialize for AperakMessage {
fn edifact_serialize<E: EventEmitter>(
&self,
emitter: &mut E,
) -> Result<(), edifact_rs::EdifactError> {
self.core.emit_segments(emitter)
}
}
impl_edi_energy_message!(AperakMessage, sem = aperak_semantic_pack());
fn parse_errors(segments: &[edifact_rs::Segment<'_>]) -> Vec<AperakError> {
let mut result = Vec::new();
let mut i = 0;
while i < segments.len() {
if segments[i].tag != "ERC" {
i += 1;
continue;
}
let Some(erc) = try_deserialize::<Erc>(&segments[i]) else {
i += 1;
continue;
};
let mut ftx = Vec::new();
let mut references = Vec::new();
let mut j = i + 1;
while j < segments.len() && segments[j].tag != "ERC" && segments[j].tag != "UNT" {
match segments[j].tag {
"FTX" => {
if let Some(f) = try_deserialize::<Ftx>(&segments[j]) {
ftx.push(f);
}
}
"RFF" => {
if let Some(r) = try_deserialize::<Rff>(&segments[j]) {
references.push(r);
}
}
_ => {}
}
j += 1;
}
result.push(AperakError {
erc,
ftx,
references,
});
i = j;
}
result
}
fn aperak_semantic_pack() -> ProfileRulePack {
ProfileRulePack::new("APERAK-SEM")
.for_message_type("APERAK")
.with_stateless_rule_fn(rule_sem_aperak_ref_missing)
}
fn rule_sem_aperak_ref_missing(
segments: &[edifact_rs::Segment<'_>],
issues: &mut Vec<ValidationIssue>,
) {
let has_acw = segments
.iter()
.filter(|s| s.tag == "RFF")
.any(|s| s.get_element(0).and_then(|e| e.get_component(0)) == Some("ACW"));
let first_rff_span = segments.iter().find(|s| s.tag == "RFF").map(|s| s.span);
if !has_acw {
let mut issue = ValidationIssue::new(
ValidationSeverity::Error,
"APERAK must contain an RFF+ACW reference to the acknowledged/rejected \
transaction (DE 1153 = ACW)",
)
.with_rule_id("SEM-APERAK-REF-MISSING")
.with_segment("RFF")
.with_suggestion(
"Add an RFF segment with DE 1153 = ACW and set DE 1154 to the \
message reference (UNH DE 0062) of the acknowledged or rejected message",
);
if let Some(span) = first_rff_span {
issue = issue.with_span(span);
}
issues.push(issue);
}
}