use std::io::{self, Read, Write};
use crate::ordinal::{Ordinal, to_usize};
const MAGIC: &[u8; 8] = b"FTIDENT\0";
const VERSION: u32 = 1;
#[derive(Debug, thiserror::Error)]
pub enum IdentifiersError {
#[error("identifiers I/O failed")]
Io(#[from] io::Error),
#[error("not an identifiers artifact")]
Magic,
#[error("identifiers layout version {found}, expected {expected}")]
Version {
found: u32,
expected: u32,
},
#[error("too many identifiers")]
TooMany,
#[error("an alternate identifier is not UTF-8")]
Text(#[from] std::string::FromUtf8Error),
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Identifiers {
entries: Vec<(u64, String, u32)>,
}
impl Identifiers {
#[must_use]
pub fn new(mut entries: Vec<(u64, String, Ordinal)>) -> Self {
entries.sort();
entries.dedup();
Self {
entries: entries
.into_iter()
.map(|(scheme, code, concept)| (scheme, code, concept.index()))
.collect(),
}
}
#[must_use]
pub fn lookup(&self, scheme: u64, code: &str) -> Option<Ordinal> {
self.entries
.binary_search_by(|(s, c, _)| (*s, c.as_str()).cmp(&(scheme, code)))
.ok()
.and_then(|i| self.entries.get(i))
.map(|(_, _, concept)| Ordinal::new(*concept))
}
#[must_use]
pub fn schemes(&self) -> Vec<u64> {
let mut schemes: Vec<u64> = self.entries.iter().map(|(s, _, _)| *s).collect();
schemes.dedup();
schemes
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn write_to(&self, out: &mut impl Write) -> Result<(), IdentifiersError> {
out.write_all(MAGIC)?;
out.write_all(&VERSION.to_le_bytes())?;
let count = u32::try_from(self.entries.len()).map_err(|_| IdentifiersError::TooMany)?;
out.write_all(&count.to_le_bytes())?;
for (scheme, code, concept) in &self.entries {
out.write_all(&scheme.to_le_bytes())?;
let len = u32::try_from(code.len()).map_err(|_| IdentifiersError::TooMany)?;
out.write_all(&len.to_le_bytes())?;
out.write_all(code.as_bytes())?;
out.write_all(&concept.to_le_bytes())?;
}
Ok(())
}
pub fn read_from(input: &mut impl Read) -> Result<Self, IdentifiersError> {
let mut magic = [0_u8; 8];
input.read_exact(&mut magic)?;
if &magic != MAGIC {
return Err(IdentifiersError::Magic);
}
let version = read_u32(input)?;
if version != VERSION {
return Err(IdentifiersError::Version {
found: version,
expected: VERSION,
});
}
let count = read_u32(input)?;
let mut entries = Vec::with_capacity(to_usize(count));
for _ in 0..count {
let mut long = [0_u8; 8];
input.read_exact(&mut long)?;
let len = to_usize(read_u32(input)?);
let mut bytes = vec![0_u8; len];
input.read_exact(&mut bytes)?;
let concept = read_u32(input)?;
entries.push((u64::from_le_bytes(long), String::from_utf8(bytes)?, concept));
}
entries.sort();
Ok(Self { entries })
}
}
fn read_u32(input: &mut impl Read) -> Result<u32, IdentifiersError> {
let mut bytes = [0_u8; 4];
input.read_exact(&mut bytes)?;
Ok(u32::from_le_bytes(bytes))
}
#[cfg(test)]
mod tests {
use super::{Identifiers, IdentifiersError};
use crate::ordinal::Ordinal;
#[test]
fn codes_resolve_per_scheme_and_the_layout_round_trips() {
let identifiers = Identifiers::new(vec![
(705_114_005, String::from("54486-6"), Ordinal::new(3)),
(705_114_005, String::from("1234-5"), Ordinal::new(4)),
(900, String::from("54486-6"), Ordinal::new(5)),
]);
assert_eq!(
identifiers.lookup(705_114_005, "54486-6"),
Some(Ordinal::new(3))
);
assert_eq!(identifiers.lookup(900, "54486-6"), Some(Ordinal::new(5)));
assert_eq!(identifiers.lookup(705_114_005, "9999"), None);
assert_eq!(identifiers.schemes(), [900, 705_114_005]);
let mut bytes = Vec::new();
identifiers.write_to(&mut bytes).expect("writes");
assert_eq!(
Identifiers::read_from(&mut bytes.as_slice()).expect("reads"),
identifiers
);
assert!(matches!(
Identifiers::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
Err(IdentifiersError::Magic)
));
}
}