#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Encoding {
pub field: u8,
pub component: u8,
pub repetition: u8,
pub escape: u8,
pub subcomponent: u8,
}
impl Default for Encoding {
fn default() -> Self {
Self {
field: b'|',
component: b'^',
repetition: b'~',
escape: b'\\',
subcomponent: b'&',
}
}
}
impl Encoding {
pub const STANDARD: Encoding = Encoding {
field: b'|',
component: b'^',
repetition: b'~',
escape: b'\\',
subcomponent: b'&',
};
pub fn from_msh2(field: u8, msh2: &[u8]) -> Option<Encoding> {
if !(4..=5).contains(&msh2.len()) {
return None;
}
let chars = &msh2[..4];
if !chars
.iter()
.all(|c| c.is_ascii() && !c.is_ascii_alphanumeric())
{
return None;
}
let all = [field, chars[0], chars[1], chars[2], chars[3]];
for (i, a) in all.iter().enumerate() {
if all[i + 1..].contains(a) {
return None;
}
}
Some(Encoding {
field,
component: chars[0],
repetition: chars[1],
escape: chars[2],
subcomponent: chars[3],
})
}
pub fn with_field(field: u8) -> Encoding {
Encoding {
field,
..Encoding::STANDARD
}
}
pub fn msh2(&self) -> String {
[
self.component,
self.repetition,
self.escape,
self.subcomponent,
]
.iter()
.map(|&b| b as char)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn standard_roundtrip() {
let e = Encoding::from_msh2(b'|', b"^~\\&").unwrap();
assert_eq!(e, Encoding::STANDARD);
assert_eq!(e.msh2(), "^~\\&");
}
#[test]
fn accepts_truncation_char() {
let e = Encoding::from_msh2(b'|', b"^~\\&#").unwrap();
assert_eq!(e, Encoding::STANDARD);
}
#[test]
fn rejects_short_duplicate_and_alnum() {
assert!(Encoding::from_msh2(b'|', b"^~\\").is_none());
assert!(Encoding::from_msh2(b'|', b"^^\\&").is_none());
assert!(Encoding::from_msh2(b'|', b"^~|&").is_none());
assert!(Encoding::from_msh2(b'|', b"a~\\&").is_none());
assert!(Encoding::from_msh2(b'|', "é~\\&".as_bytes()).is_none());
}
}