#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UnaParseError {
InvalidLength { expected: usize, actual: usize },
InvalidPrefix,
}
impl std::fmt::Display for UnaParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidLength { expected, actual } => {
write!(
f,
"UNA segment must be exactly {expected} bytes, got {actual}"
)
}
Self::InvalidPrefix => write!(f, "UNA segment must start with 'UNA'"),
}
}
}
impl std::error::Error for UnaParseError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EdifactDelimiters {
pub component: u8,
pub element: u8,
pub decimal: u8,
pub release: u8,
pub segment: u8,
pub reserved: u8,
}
impl Default for EdifactDelimiters {
fn default() -> Self {
Self {
component: b':',
element: b'+',
decimal: b'.',
release: b'?',
segment: b'\'',
reserved: b' ',
}
}
}
impl EdifactDelimiters {
pub const STANDARD: Self = Self {
component: b':',
element: b'+',
decimal: b'.',
release: b'?',
segment: b'\'',
reserved: b' ',
};
pub fn from_una(una: &[u8]) -> Result<Self, UnaParseError> {
if una.len() != 9 {
return Err(UnaParseError::InvalidLength {
expected: 9,
actual: una.len(),
});
}
if &una[0..3] != b"UNA" {
return Err(UnaParseError::InvalidPrefix);
}
Ok(Self {
component: una[3],
element: una[4],
decimal: una[5],
release: una[6],
reserved: una[7],
segment: una[8],
})
}
pub fn detect(input: &[u8]) -> (bool, Self) {
if input.len() >= 9 && &input[0..3] == b"UNA" {
match Self::from_una(&input[0..9]) {
Ok(d) => (true, d),
Err(_) => (false, Self::default()),
}
} else {
(false, Self::default())
}
}
pub fn to_una_string(&self) -> String {
format!(
"UNA{}{}{}{}{}{}",
self.component as char,
self.element as char,
self.decimal as char,
self.release as char,
self.reserved as char,
self.segment as char,
)
}
}
impl std::fmt::Display for EdifactDelimiters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"UNA{}{}{}{}{}{}",
self.component as char,
self.element as char,
self.decimal as char,
self.release as char,
self.reserved as char,
self.segment as char,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_delimiters() {
let d = EdifactDelimiters::default();
assert_eq!(d.component, b':');
assert_eq!(d.element, b'+');
assert_eq!(d.decimal, b'.');
assert_eq!(d.release, b'?');
assert_eq!(d.segment, b'\'');
assert_eq!(d.reserved, b' ');
}
#[test]
fn test_delimiters_equality() {
let a = EdifactDelimiters::default();
let b = EdifactDelimiters::default();
assert_eq!(a, b);
}
#[test]
fn test_delimiters_debug() {
let d = EdifactDelimiters::default();
let debug = format!("{:?}", d);
assert!(debug.contains("EdifactDelimiters"));
}
#[test]
fn test_from_una_standard() {
let una = b"UNA:+.? '";
let d = EdifactDelimiters::from_una(una).unwrap();
assert_eq!(d, EdifactDelimiters::default());
}
#[test]
fn test_from_una_custom_delimiters() {
let una = b"UNA;*.# |";
let d = EdifactDelimiters::from_una(una).unwrap();
assert_eq!(d.component, b';');
assert_eq!(d.element, b'*');
assert_eq!(d.decimal, b'.');
assert_eq!(d.release, b'#');
assert_eq!(d.reserved, b' ');
assert_eq!(d.segment, b'|');
}
#[test]
fn test_from_una_too_short() {
let una = b"UNA:+.";
assert!(EdifactDelimiters::from_una(una).is_err());
}
#[test]
fn test_from_una_wrong_prefix() {
let una = b"XXX:+.? '";
assert!(EdifactDelimiters::from_una(una).is_err());
}
#[test]
fn test_detect_with_una() {
let input = b"UNA:+.? 'UNB+UNOC:3+sender+recipient'";
let (has_una, delimiters) = EdifactDelimiters::detect(input);
assert!(has_una);
assert_eq!(delimiters, EdifactDelimiters::default());
}
#[test]
fn test_detect_without_una() {
let input = b"UNB+UNOC:3+sender+recipient'";
let (has_una, delimiters) = EdifactDelimiters::detect(input);
assert!(!has_una);
assert_eq!(delimiters, EdifactDelimiters::default());
}
#[test]
fn test_detect_empty_input() {
let input = b"";
let (has_una, delimiters) = EdifactDelimiters::detect(input);
assert!(!has_una);
assert_eq!(delimiters, EdifactDelimiters::default());
}
#[test]
fn test_una_roundtrip() {
let original = EdifactDelimiters {
component: b';',
element: b'*',
decimal: b',',
release: b'#',
segment: b'!',
reserved: b' ',
};
let una_string = original.to_una_string();
let parsed = EdifactDelimiters::from_una(una_string.as_bytes()).unwrap();
assert_eq!(original, parsed);
}
}