use crate::{EdifactError, Segment};
use std::borrow::Cow;
use std::io::Read;
pub trait EdifactDeserialize: Sized {
fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError>;
}
pub trait EdifactCompositeDeserialize: Sized {
fn edifact_deserialize_composite(composite: CompositeElement<'_>)
-> Result<Self, EdifactError>;
}
impl EdifactCompositeDeserialize for Vec<String> {
fn edifact_deserialize_composite(
composite: CompositeElement<'_>,
) -> Result<Self, EdifactError> {
Ok(composite.iter().map(str::to_owned).collect())
}
}
pub trait EdifactSegmentTag {
const SEGMENT_TAG: &'static str;
const QUALIFIER_PATTERN: Option<&'static str> = None;
fn matches_qualifier(seg: &Segment<'_>) -> bool {
match Self::QUALIFIER_PATTERN {
Some(pattern) => qualifier_matches_pattern(seg.element_str(0).unwrap_or(""), pattern),
None => true,
}
}
fn matches_segment(seg: &Segment<'_>) -> bool {
seg.tag == Self::SEGMENT_TAG && Self::matches_qualifier(seg)
}
}
impl<T> EdifactDeserialize for Vec<T>
where
T: EdifactDeserialize + EdifactSegmentTag,
{
fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
segments
.iter()
.filter(|s| T::matches_segment(s))
.map(|seg| T::edifact_deserialize(std::slice::from_ref(seg)))
.collect()
}
}
pub fn deserialize<T: EdifactDeserialize>(input: &[u8]) -> Result<T, EdifactError> {
let segments: Vec<Segment<'_>> = crate::from_bytes(input).collect::<Result<_, _>>()?;
T::edifact_deserialize(&segments)
}
pub fn deserialize_str<T: EdifactDeserialize>(input: &str) -> Result<T, EdifactError> {
deserialize(input.as_bytes())
}
#[cfg_attr(feature = "derive", doc = "```")]
#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
pub fn deserialize_each<'a, T>(
input: &'a [u8],
) -> impl Iterator<Item = Result<T, EdifactError>> + 'a
where
T: EdifactDeserialize + EdifactSegmentTag + 'a,
{
deserialize_matching(crate::from_bytes(input))
}
pub fn deserialize_each_from_reader<T, R>(
reader: R,
) -> impl Iterator<Item = Result<T, EdifactError>>
where
T: EdifactDeserialize + EdifactSegmentTag,
R: Read,
{
deserialize_matching(crate::from_reader(reader))
}
fn deserialize_matching<'a, T, I>(segments: I) -> impl Iterator<Item = Result<T, EdifactError>>
where
T: EdifactDeserialize + EdifactSegmentTag,
I: Iterator<Item = Result<Segment<'a>, EdifactError>>,
{
segments.filter_map(|segment| match segment {
Ok(segment) if T::matches_segment(&segment) => {
Some(T::edifact_deserialize(std::slice::from_ref(&segment)))
}
Ok(_) => None,
Err(error) => Some(Err(error)),
})
}
pub fn find_segment<'s, 'd>(segments: &'s [Segment<'d>], tag: &str) -> Option<&'s Segment<'d>> {
segments.iter().find(|s| s.tag == tag)
}
pub fn find_segments<'s, 'd: 's>(
segments: &'s [Segment<'d>],
tag: &'s str,
) -> impl Iterator<Item = &'s Segment<'d>> {
segments.iter().filter(move |s| s.tag == tag)
}
pub fn find_qualified_segment<'s, 'd>(
segments: &'s [Segment<'d>],
tag: &str,
qualifier: &str,
) -> Option<&'s Segment<'d>> {
segments
.iter()
.find(|s| s.tag == tag && s.element_str(0).unwrap_or("") == qualifier)
}
pub fn find_segments_typed<'s, 'd: 's, T>(
segments: &'s [Segment<'d>],
) -> impl Iterator<Item = &'s Segment<'d>>
where
T: EdifactSegmentTag,
{
segments.iter().filter(|s| T::matches_segment(s))
}
pub fn contiguous_groups<'s, 'd, T>(
segments: &'s [Segment<'d>],
) -> impl Iterator<Item = &'s [Segment<'d>]> + 's
where
T: EdifactSegmentTag,
{
let mut idx = 0;
let len = segments.len();
std::iter::from_fn(move || {
while idx < len && !T::matches_segment(&segments[idx]) {
idx += 1;
}
if idx >= len {
return None;
}
let start = idx;
idx += 1;
while idx < len && T::matches_segment(&segments[idx]) {
idx += 1;
}
Some(&segments[start..idx])
})
}
pub fn qualifier_matches_pattern(value: &str, pattern: &str) -> bool {
if pattern.is_empty() {
return value.is_empty();
}
if !pattern.contains('*') {
return value == pattern;
}
if let Some((prefix, suffix)) = pattern.split_once('*') {
if !suffix.contains('*') {
return value.len() >= prefix.len() + suffix.len()
&& value.starts_with(prefix)
&& value.ends_with(suffix);
}
}
let parts: smallvec::SmallVec<[&str; 4]> = pattern.split('*').collect();
if parts.len() > 4 {
return false;
}
let prefix = parts[0];
let suffix = parts[parts.len() - 1];
if !value.starts_with(prefix) || !value.ends_with(suffix) {
return false;
}
let mid_start = prefix.len();
let mid_end = value.len().saturating_sub(suffix.len());
if mid_start > mid_end {
return parts[1..parts.len() - 1].iter().all(|p| p.is_empty());
}
let mut remaining = &value[mid_start..mid_end];
for part in &parts[1..parts.len() - 1] {
if part.is_empty() {
continue;
}
match remaining.find(part) {
Some(idx) => remaining = &remaining[idx + part.len()..],
None => return false,
}
}
true
}
pub struct CompositeElement<'a> {
components: smallvec::SmallVec<[&'a str; 4]>,
}
impl<'a> CompositeElement<'a> {
pub fn from_slice(components: &'a [Cow<'a, str>]) -> Self {
Self {
components: components.iter().map(|c| c.as_ref()).collect(),
}
}
pub(crate) fn from_strs(components: smallvec::SmallVec<[&'a str; 4]>) -> Self {
Self { components }
}
pub fn get(&self, i: usize) -> Option<&'a str> {
self.components.get(i).copied()
}
pub fn get_or_empty(&self, i: usize) -> &'a str {
self.get(i).unwrap_or("")
}
pub fn len(&self) -> usize {
self.components.len()
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
self.components.iter().copied()
}
}
pub fn composite_element<'a, 'd: 'a>(
seg: &'a Segment<'d>,
idx: usize,
) -> Option<CompositeElement<'a>> {
seg.elements
.get(idx)
.map(|elem| CompositeElement::from_strs(elem.components().collect()))
}
#[derive(Debug, Clone)]
pub struct MessageWindow<'a> {
pub message_type: Option<Cow<'a, str>>,
pub association_code: Option<Cow<'a, str>>,
pub segments: Vec<Segment<'a>>,
}
pub type OwnedMessageWindow = MessageWindow<'static>;
impl<'a> MessageWindow<'a> {
#[must_use]
pub fn body(&self) -> &[Segment<'a>] {
let start = usize::from(self.segments.first().is_some_and(|s| s.tag == "UNH"));
let end = self.segments.len().saturating_sub(usize::from(
self.segments.last().is_some_and(|s| s.tag == "UNT"),
));
self.segments.get(start..end).unwrap_or(&[])
}
fn from_segments(segments: Vec<Segment<'a>>) -> Self {
let unh = segments.first().filter(|s| s.tag == "UNH");
let component = |idx: usize| -> Option<Cow<'a, str>> {
unh?.elements
.get(1)?
.components
.get(idx)
.map(|(c, _)| c.clone())
.filter(|c| !c.is_empty())
};
Self {
message_type: component(0),
association_code: component(4),
segments,
}
}
}
pub struct MessageWindows<'a, I> {
inner: I,
buf: Vec<Segment<'a>>,
in_message: bool,
done: bool,
}
impl<'a, I> MessageWindows<'a, I>
where
I: Iterator<Item = Result<Segment<'a>, EdifactError>>,
{
pub fn new(inner: I) -> Self {
Self {
inner,
buf: Vec::new(),
in_message: false,
done: false,
}
}
}
impl<'a, I> Iterator for MessageWindows<'a, I>
where
I: Iterator<Item = Result<Segment<'a>, EdifactError>>,
{
type Item = Result<MessageWindow<'a>, EdifactError>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
loop {
let segment = match self.inner.next() {
Some(Ok(s)) => s,
Some(Err(e)) => {
self.done = true;
return Some(Err(e));
}
None => {
self.done = true;
if self.in_message && !self.buf.is_empty() {
self.in_message = false;
let offset = self.buf.last().map(|s| s.span.end).unwrap_or(0);
return Some(Err(EdifactError::UnexpectedEof { offset }));
}
return None;
}
};
match segment.tag() {
"UNH" => {
if self.in_message {
self.buf.clear();
self.in_message = false;
self.done = true;
return Some(Err(EdifactError::InvalidSegmentForMessage {
tag: "UNH".to_owned(),
message_type: "ENVELOPE".to_owned(),
span: segment.span,
}));
}
self.buf.clear();
self.in_message = true;
self.buf.push(segment);
}
"UNT" if self.in_message => {
self.buf.push(segment);
self.in_message = false;
let segments = std::mem::take(&mut self.buf);
return Some(Ok(MessageWindow::from_segments(segments)));
}
_ if self.in_message => self.buf.push(segment),
_ => {}
}
}
}
}
pub fn message_windows(input: &[u8]) -> MessageWindows<'_, crate::FromBytesIter<'_>> {
MessageWindows::new(crate::from_bytes(input))
}
pub fn message_windows_from_reader<R: Read>(
reader: R,
) -> MessageWindows<'static, crate::FromReaderIter<R>> {
MessageWindows::new(crate::from_reader(reader))
}
#[cfg_attr(feature = "derive", doc = "```")]
#[cfg_attr(not(feature = "derive"), doc = "```ignore")]
pub fn deserialize_messages<'a, T>(
input: &'a [u8],
) -> impl Iterator<Item = Result<T, EdifactError>> + 'a
where
T: EdifactDeserialize + 'a,
{
message_windows(input).map(|window| T::edifact_deserialize(&window?.segments))
}
pub fn deserialize_messages_from_reader<T, R>(
reader: R,
) -> impl Iterator<Item = Result<T, EdifactError>>
where
T: EdifactDeserialize,
R: Read,
{
message_windows_from_reader(reader).map(|window| T::edifact_deserialize(&window?.segments))
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct BgmSegment {
doc_name_code: String,
pruef_id: String,
msg_function: Option<String>,
}
impl EdifactSegmentTag for BgmSegment {
const SEGMENT_TAG: &'static str = "BGM";
}
struct NadM;
impl EdifactSegmentTag for NadM {
const SEGMENT_TAG: &'static str = "NAD";
const QUALIFIER_PATTERN: Option<&'static str> = Some("M*");
}
impl EdifactDeserialize for BgmSegment {
fn edifact_deserialize(segments: &[Segment<'_>]) -> Result<Self, EdifactError> {
let seg = find_segment(segments, "BGM").ok_or_else(|| {
EdifactError::MissingRequiredElement {
tag: "BGM".to_owned(),
element_index: 0,
}
})?;
Ok(Self {
doc_name_code: seg.element_str(0).unwrap_or("").to_owned(),
pruef_id: seg.element_str(1).unwrap_or("").to_owned(),
msg_function: seg.optional_element(2).map(str::to_owned),
})
}
}
#[test]
fn deserialize_single_segment() {
let input = b"BGM+E03+11042+9'";
let bgm: BgmSegment = deserialize(input).unwrap();
assert_eq!(bgm.doc_name_code, "E03");
assert_eq!(bgm.pruef_id, "11042");
assert_eq!(bgm.msg_function, Some("9".to_owned()));
}
#[test]
fn deserialize_each_is_lazy_over_both_sources() {
let input = b"BGM+E03+11042+9'RFF+AA:1'BGM+E01+11043+9'";
let from_slice: Vec<BgmSegment> =
deserialize_each(input).collect::<Result<_, _>>().unwrap();
let from_reader: Vec<BgmSegment> =
deserialize_each_from_reader(std::io::Cursor::new(&input[..]))
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(from_slice, from_reader);
assert_eq!(from_slice.len(), 2);
assert_eq!(from_slice[1].pruef_id, "11043");
}
#[test]
fn deserialize_each_stops_at_the_first_match_when_asked() {
let input = b"UNH+1+ORDERS:D:11A:UN'BGM+E03+11042+9'UNT+3+1'";
let first: BgmSegment = deserialize_each(input).next().unwrap().unwrap();
assert_eq!(first.pruef_id, "11042");
}
#[test]
fn qualifier_patterns_match_the_documented_way() {
assert!(qualifier_matches_pattern("MS", "M*"));
assert!(!qualifier_matches_pattern("MS", "M"));
assert!(qualifier_matches_pattern("MS", "MS"));
assert!(qualifier_matches_pattern("", ""));
assert!(!qualifier_matches_pattern("aaaa", "*a*a*a*a*"));
}
#[test]
fn typed_qualifier_matching_filters_by_element_zero() {
let segments: Vec<Segment<'_>> = crate::from_bytes(b"NAD+MS+1'NAD+BY+2'NAD+MR+3'")
.collect::<Result<_, _>>()
.unwrap();
let matched: Vec<&str> = find_segments_typed::<NadM>(&segments)
.map(|s| s.element_str(1).unwrap())
.collect();
assert_eq!(matched, ["1", "3"]);
}
#[test]
fn message_windows_agree_across_both_parsing_paths() {
let input = b"UNB+UNOA:1+S+R+200101:0900+1'\
UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'UNT+3+1'\
UNH+2+ORDERS:D:96A:UN'BGM+220+B+9'UNT+3+2'\
UNZ+2+1'";
let sliced: Vec<_> = message_windows(input)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let streamed: Vec<_> = message_windows_from_reader(std::io::Cursor::new(&input[..]))
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(sliced.len(), 2);
assert_eq!(sliced.len(), streamed.len());
for (a, b) in sliced.iter().zip(&streamed) {
assert_eq!(a.message_type, b.message_type);
assert_eq!(a.body().len(), b.body().len());
}
}
#[test]
fn a_truncated_window_is_an_error_not_a_short_message() {
let input = b"UNH+1+ORDERS:D:96A:UN'BGM+220+A+9'";
let err = message_windows(input)
.collect::<Result<Vec<_>, _>>()
.expect_err("an unclosed UNH must not pass as a complete message");
assert!(matches!(err, EdifactError::UnexpectedEof { .. }));
}
}