#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(unsafe_code)]
pub mod charset;
pub mod contrl;
pub mod directory_validator;
pub(crate) mod envelope;
pub(crate) mod error;
pub mod group;
pub(crate) mod model;
pub(crate) mod parser;
pub mod report;
pub mod service;
pub(crate) mod tokenizer;
pub(crate) mod validator;
pub(crate) mod writer;
pub mod de;
pub(crate) mod event;
pub mod ser;
pub use charset::{Charset, DecodingReader, decode_interchange, decode_reader, sniff_charset};
pub use contrl::{Action, Contrl, ReportingLevel, SyntaxError};
pub use envelope::{
FunctionalGroupEnvelope, GroupIdentifier, InterchangeEnvelope, LenientResult, MessageEnvelope,
MessageIdentifier, ValidatedInterchange, parse_ung, parse_unh, validate_envelope,
validate_envelope_lenient,
};
pub use error::{EdifactError, Insignificant, IoError};
pub use group::{Descendants, GroupDef, SegmentGroupIndexed, group_segments_indexed};
pub use model::{Components, Element, OwnedElement, OwnedSegment, Segment, Span};
pub use parser::{
OwnedSegmentStream, Parser, ReaderConfig, from_bufread, from_bufread_with_config,
from_reader_with_config,
};
pub use report::severity_for_error;
pub use report::{ValidationIssue, ValidationReport, ValidationSeverity};
pub use tokenizer::{ServiceStringAdvice, Token, Tokenizer};
pub use validator::{
CharsetValidator, EnvelopeValidator, ProfileRule, ProfileRulePack, SyntaxValidator,
ValidationContext, ValidationContextBuilder, ValidationLayer, ValidationRuleContext, Validator,
validate_each,
};
pub use writer::{AsDataElement, DataElement, MessageWriter, Writer};
pub use de::{
CompositeElement, EdifactCompositeDeserialize, EdifactDeserialize, EdifactSegmentTag,
MessageWindow, MessageWindows, OwnedMessageWindow, composite_element, contiguous_groups,
deserialize, deserialize_each, deserialize_each_from_reader, deserialize_messages,
deserialize_messages_from_reader, deserialize_str, find_qualified_segment, find_segment,
find_segments, find_segments_typed, message_windows, message_windows_from_reader,
qualifier_matches_pattern,
};
pub use directory_validator::{
ComponentRef, DirectoryValidator, DirectoryValidatorBuilder, ElementPath, ElementRef,
LayoutAudit, LayoutFinding, LayoutSlot, OwnedComponentRef, OwnedElementRef, OwnedSegmentDef,
Repr, ReprKind, SegmentDefinition, SegmentLayout, Status, audit_directory,
};
#[cfg(feature = "derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use edifact_rs_derive::{
EdifactCompositeDeserialize, EdifactCompositeSerialize, EdifactDeserialize, EdifactSerialize,
};
pub use event::{EdifactEvent, EventEmitter, VecEmitter, WriterEmitter};
pub use ser::{
DecimalFloat, EdifactCompositeSerialize, EdifactSerialize, emit_sparse_segment, to_bytes,
to_edifact_string,
};
use std::io::{Read, Write};
pub struct FromBytesIter<'a> {
parser: Option<parser::Parser<'a>>,
pending_error: Option<EdifactError>,
config: ReaderConfig,
segments_yielded: usize,
messages_yielded: usize,
in_message: bool,
}
pub struct FromReaderIter<R: Read> {
inner: parser::OwnedSegmentStream<std::io::BufReader<R>>,
}
impl<R: Read> Iterator for FromReaderIter<R> {
type Item = Result<OwnedSegment, EdifactError>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
impl<'a> Iterator for FromBytesIter<'a> {
type Item = Result<Segment<'a>, EdifactError>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(err) = self.pending_error.take() {
self.parser = None;
return Some(Err(err));
}
let seg = match self.parser.as_mut()?.next()? {
Ok(seg) => seg,
Err(error) => {
self.parser = None;
return Some(Err(error));
}
};
if let Some(max) = self.config.max_segments {
if self.segments_yielded >= max {
return Some(Err(self.exceeded("max_segments", max as u64)));
}
}
if let Some(max) = self.config.max_messages {
if seg.tag == "UNH" && self.messages_yielded >= max {
return Some(Err(self.exceeded("max_messages", max as u64)));
}
}
if let Some(max) = self.config.max_input_bytes {
if seg.span.end as u64 > max {
return Some(Err(self.exceeded("max_input_bytes", max)));
}
}
self.segments_yielded += 1;
if seg.tag == "UNT" {
if self.in_message {
self.messages_yielded += 1;
}
self.in_message = false;
} else if seg.tag == "UNH" {
self.in_message = true;
}
Some(Ok(seg))
}
}
impl FromBytesIter<'_> {
#[inline]
fn exceeded(&mut self, limit: &'static str, max: u64) -> EdifactError {
self.parser = None;
EdifactError::LimitExceeded { limit, max }
}
}
pub fn from_bytes(input: &[u8]) -> FromBytesIter<'_> {
from_bytes_with_config(input, parser::ReaderConfig::default())
}
pub fn from_bytes_with_config(input: &[u8], config: parser::ReaderConfig) -> FromBytesIter<'_> {
let discovered = tokenizer::ServiceStringAdvice::from_bytes(input);
let resolved = match (config.service_string_advice, discovered) {
(_, Err(error)) => Err(error),
(Some(override_ssa), Ok(_)) => Ok(override_ssa),
(None, Ok(ssa)) => Ok(ssa),
};
let (parser, pending_error) = match resolved {
Ok(ssa) => {
let t = tokenizer::Tokenizer::with_limit(input, ssa, config.max_segment_bytes);
(Some(parser::Parser::new(t)), None)
}
Err(error) => (None, Some(error)),
};
FromBytesIter {
parser,
pending_error,
config,
segments_yielded: 0,
messages_yielded: 0,
in_message: false,
}
}
pub fn from_reader<R: Read>(reader: R) -> FromReaderIter<R> {
FromReaderIter {
inner: parser::from_reader_stream(reader),
}
}
pub fn from_bytes_decoded(input: &[u8]) -> Result<Vec<OwnedSegment>, EdifactError> {
from_bytes_decoded_with_config(input, ReaderConfig::default())
}
pub fn from_bytes_decoded_with_config(
input: &[u8],
config: ReaderConfig,
) -> Result<Vec<OwnedSegment>, EdifactError> {
let decoded = charset::decode_interchange(input)?;
from_bytes_with_config(&decoded, config)
.map(|r| r.map(|segment| segment.into_owned()))
.collect()
}
pub fn from_reader_decoded<R: Read>(reader: R) -> DecodingSegmentStream<R> {
from_reader_decoded_with_config(reader, ReaderConfig::default())
}
pub fn from_reader_decoded_with_config<R: Read>(
reader: R,
config: ReaderConfig,
) -> DecodingSegmentStream<R> {
DecodingSegmentStream {
state: DecodingState::Pending(reader),
config,
}
}
pub struct DecodingSegmentStream<R: Read> {
state: DecodingState<R>,
config: ReaderConfig,
}
type DecodedReader<R> = charset::DecodingReader<std::io::Chain<std::io::Cursor<Vec<u8>>, R>>;
enum DecodingState<R: Read> {
Pending(R),
Running(Box<parser::OwnedSegmentStream<std::io::BufReader<DecodedReader<R>>>>),
Done,
}
impl<R: Read> Iterator for DecodingSegmentStream<R> {
type Item = Result<OwnedSegment, EdifactError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match &mut self.state {
DecodingState::Done => return None,
DecodingState::Running(stream) => return stream.next(),
DecodingState::Pending(_) => {
let DecodingState::Pending(reader) =
std::mem::replace(&mut self.state, DecodingState::Done)
else {
unreachable!("guarded by the match arm")
};
match charset::decode_reader(reader) {
Ok(decoded) => {
self.state = DecodingState::Running(Box::new(
parser::from_reader_with_config(decoded, self.config),
));
}
Err(error) => return Some(Err(error)),
}
}
}
}
}
}
pub fn to_writer<'a, 'b, W, I>(w: W, segments: I) -> Result<(), EdifactError>
where
'b: 'a,
W: Write,
I: IntoIterator<Item = &'a Segment<'b>>,
{
let mut wr = writer::Writer::new(w);
for seg in segments {
wr.write_segment(seg)?;
}
wr.finish().map(|_| ())
}
pub fn segments_to_bytes<'a, 'b, I>(segments: I) -> Result<Vec<u8>, EdifactError>
where
'b: 'a,
I: IntoIterator<Item = &'a Segment<'b>>,
{
let mut buf = Vec::new();
to_writer(&mut buf, segments)?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_bytes_rejects_invalid_una() {
let err = from_bytes(b"UNA::.? 'BGM:220'")
.collect::<Result<Vec<_>, _>>()
.expect_err("invalid UNA should fail slice parsing");
assert!(matches!(err, EdifactError::InvalidUna));
}
}
#[cfg(doctest)]
mod doc_guides {
macro_rules! guide {
($name:ident, $path:literal) => {
#[doc = include_str!($path)]
pub struct $name;
};
}
guide!(
CharacterSets,
"../../../site/content/docs/character-sets.md"
);
guide!(Contrl, "../../../site/content/docs/contrl.md");
guide!(CoreConcepts, "../../../site/content/docs/core-concepts.md");
guide!(Parsing, "../../../site/content/docs/parsing.md");
guide!(ProfilePacks, "../../../site/content/docs/profile-packs.md");
guide!(Validation, "../../../site/content/docs/validation.md");
#[cfg(feature = "derive")]
guide!(
AsyncIntegration,
"../../../site/content/docs/async-integration.md"
);
#[cfg(feature = "derive")]
guide!(
ErrorReference,
"../../../site/content/docs/error-reference.md"
);
#[cfg(feature = "derive")]
guide!(
GettingStarted,
"../../../site/content/docs/getting-started.md"
);
#[cfg(feature = "derive")]
guide!(Performance, "../../../site/content/docs/performance.md");
#[cfg(feature = "derive")]
guide!(Streaming, "../../../site/content/docs/streaming.md");
#[cfg(feature = "derive")]
guide!(TypedDerive, "../../../site/content/docs/typed-derive.md");
#[cfg(feature = "derive")]
guide!(Writing, "../../../site/content/docs/writing.md");
#[cfg(feature = "diagnostics")]
guide!(Diagnostics, "../../../site/content/docs/diagnostics.md");
#[cfg(feature = "derive")]
guide!(Readme, "../../../README.md");
}