use crate::EdifactError;
use std::io::Write;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum EdifactEvent<'a> {
StartSegment {
tag: &'a str,
},
Element {
value: &'a str,
},
ComponentElement {
value: &'a str,
},
EndSegment,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OwnedEdifactEvent {
StartSegment {
tag: String,
},
Element {
value: String,
},
ComponentElement {
value: String,
},
EndSegment,
}
impl<'a> EdifactEvent<'a> {
pub fn into_owned(self) -> OwnedEdifactEvent {
match self {
Self::StartSegment { tag } => OwnedEdifactEvent::StartSegment {
tag: tag.to_owned(),
},
Self::Element { value } => OwnedEdifactEvent::Element {
value: value.to_owned(),
},
Self::ComponentElement { value } => OwnedEdifactEvent::ComponentElement {
value: value.to_owned(),
},
Self::EndSegment => OwnedEdifactEvent::EndSegment,
}
}
}
pub trait EventEmitter {
fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError>;
#[inline]
fn decimal_mark(&self) -> u8 {
b'.'
}
}
#[derive(Debug, Default)]
pub struct VecEmitter {
pub events: Vec<OwnedEdifactEvent>,
}
impl EventEmitter for VecEmitter {
fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
self.events.push(event.into_owned());
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EmitterState {
Idle,
InSegment,
InElement,
}
pub struct WriterEmitter<W: Write> {
writer: crate::Writer<W>,
state: EmitterState,
}
impl<W: Write> WriterEmitter<W> {
pub fn new(inner: W) -> Self {
Self {
writer: crate::Writer::new(inner),
state: EmitterState::Idle,
}
}
pub fn with_una(
inner: W,
ssa: crate::tokenizer::ServiceStringAdvice,
) -> Result<Self, crate::EdifactError> {
Ok(Self {
writer: crate::Writer::with_una(inner, ssa)?,
state: EmitterState::Idle,
})
}
pub fn finish(self) -> Result<W, EdifactError> {
self.writer.finish()
}
pub fn segment_count(&self) -> u64 {
self.writer.segment_count()
}
pub fn service_string_advice(&self) -> crate::tokenizer::ServiceStringAdvice {
self.writer.service_string_advice()
}
}
impl<W: Write> EventEmitter for WriterEmitter<W> {
#[inline]
fn decimal_mark(&self) -> u8 {
self.writer.service_string_advice().decimal_mark
}
fn emit(&mut self, event: EdifactEvent<'_>) -> Result<(), EdifactError> {
match event {
EdifactEvent::StartSegment { tag } => {
if self.state != EmitterState::Idle {
return Err(EdifactError::InvalidEventSequence {
message: "StartSegment emitted while a segment is already open; emit EndSegment first",
});
}
self.state = EmitterState::InSegment;
self.writer.write_tag_only(tag)?;
}
EdifactEvent::Element { value } => {
if self.state == EmitterState::Idle {
return Err(EdifactError::InvalidEventSequence {
message: "Element emitted outside of a segment; emit StartSegment first",
});
}
self.state = EmitterState::InElement;
self.writer.write_element_sep()?;
self.writer.write_escaped(value)?;
}
EdifactEvent::ComponentElement { value } => {
if self.state != EmitterState::InElement {
return Err(EdifactError::InvalidEventSequence {
message: "ComponentElement emitted without a preceding Element in the same segment",
});
}
self.writer.write_component_sep()?;
self.writer.write_escaped(value)?;
}
EdifactEvent::EndSegment => {
if self.state == EmitterState::Idle {
return Err(EdifactError::InvalidEventSequence {
message: "EndSegment emitted while no segment is open; emit StartSegment first",
});
}
self.state = EmitterState::Idle;
self.writer.write_segment_term_and_count()?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vec_emitter_no_memory_leak() {
let mut e = VecEmitter::default();
e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
e.emit(EdifactEvent::Element { value: "E03" }).unwrap();
e.emit(EdifactEvent::EndSegment).unwrap();
assert_eq!(
e.events[0],
OwnedEdifactEvent::StartSegment {
tag: "BGM".to_owned()
}
);
assert_eq!(
e.events[1],
OwnedEdifactEvent::Element {
value: "E03".to_owned()
}
);
}
#[test]
fn writer_emitter_produces_valid_edifact() {
let mut buf = Vec::new();
{
let mut e = WriterEmitter::new(&mut buf);
e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
e.emit(EdifactEvent::Element { value: "E03" }).unwrap();
e.emit(EdifactEvent::Element { value: "11042" }).unwrap();
e.emit(EdifactEvent::EndSegment).unwrap();
e.finish().unwrap();
}
assert_eq!(buf, b"BGM+E03+11042'");
}
#[test]
fn writer_emitter_handles_components() {
let mut buf = Vec::new();
{
let mut e = WriterEmitter::new(&mut buf);
e.emit(EdifactEvent::StartSegment { tag: "NAD" }).unwrap();
e.emit(EdifactEvent::Element { value: "MS" }).unwrap();
e.emit(EdifactEvent::Element {
value: "9900112233445",
})
.unwrap();
e.emit(EdifactEvent::ComponentElement { value: "" })
.unwrap();
e.emit(EdifactEvent::ComponentElement { value: "293" })
.unwrap();
e.emit(EdifactEvent::EndSegment).unwrap();
e.finish().unwrap();
}
let s = std::str::from_utf8(&buf).unwrap();
assert_eq!(s, "NAD+MS+9900112233445::293'");
}
#[test]
fn writer_emitter_element_before_start_segment_is_err() {
let mut e = WriterEmitter::new(Vec::<u8>::new());
let err = e.emit(EdifactEvent::Element { value: "X" }).unwrap_err();
assert!(
matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
"expected InvalidEventSequence, got {err:?}"
);
}
#[test]
fn writer_emitter_component_before_element_is_err() {
let mut e = WriterEmitter::new(Vec::<u8>::new());
e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
let err = e
.emit(EdifactEvent::ComponentElement { value: "X" })
.unwrap_err();
assert!(
matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
"expected InvalidEventSequence, got {err:?}"
);
}
#[test]
fn writer_emitter_double_start_segment_is_err() {
let mut e = WriterEmitter::new(Vec::<u8>::new());
e.emit(EdifactEvent::StartSegment { tag: "BGM" }).unwrap();
let err = e
.emit(EdifactEvent::StartSegment { tag: "DTM" })
.unwrap_err();
assert!(
matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
"expected InvalidEventSequence, got {err:?}"
);
}
#[test]
fn writer_emitter_end_segment_without_start_is_err() {
let mut e = WriterEmitter::new(Vec::<u8>::new());
let err = e.emit(EdifactEvent::EndSegment).unwrap_err();
assert!(
matches!(err, crate::EdifactError::InvalidEventSequence { .. }),
"expected InvalidEventSequence, got {err:?}"
);
}
}