use std::collections::HashMap;
use std::fmt;
use crate::error::FmIndexError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SeqId(pub u32);
impl SeqId {
pub const fn new(id: u32) -> Self {
Self(id)
}
pub const fn get(self) -> u32 {
self.0
}
pub const fn index(self) -> usize {
self.0 as usize
}
}
impl fmt::Display for SeqId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u32> for SeqId {
fn from(id: u32) -> Self {
Self(id)
}
}
impl From<SeqId> for u32 {
fn from(id: SeqId) -> Self {
id.0
}
}
impl From<SeqId> for usize {
fn from(id: SeqId) -> Self {
id.0 as usize
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct HeaderIndex {
by_header: HashMap<Box<str>, u32>,
}
impl HeaderIndex {
pub(crate) fn build(headers: &[String]) -> Result<Self, FmIndexError> {
let mut by_header: HashMap<Box<str>, u32> = HashMap::with_capacity(headers.len());
for (i, header) in headers.iter().enumerate() {
if by_header.insert(header.as_str().into(), i as u32).is_some() {
return Err(FmIndexError::DuplicateHeader(header.clone()));
}
}
Ok(Self { by_header })
}
pub(crate) fn get(&self, header: &str) -> Option<SeqId> {
self.by_header.get(header).copied().map(SeqId)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seq_id_conversions_round_trip() {
let id = SeqId::new(7);
assert_eq!(id.get(), 7);
assert_eq!(id.index(), 7usize);
assert_eq!(u32::from(id), 7);
assert_eq!(usize::from(id), 7usize);
assert_eq!(SeqId::from(7u32), id);
assert_eq!(id.to_string(), "7");
}
#[test]
fn header_index_maps_headers_to_build_order() {
let headers = vec!["chr1".to_string(), "chr2".to_string(), "chrX".to_string()];
let index = HeaderIndex::build(&headers).unwrap();
assert_eq!(index.get("chr1"), Some(SeqId(0)));
assert_eq!(index.get("chr2"), Some(SeqId(1)));
assert_eq!(index.get("chrX"), Some(SeqId(2)));
assert_eq!(index.get("chrY"), None);
assert_eq!(index.get(""), None);
}
#[test]
fn header_index_rejects_duplicates() {
let headers = vec!["chr1".to_string(), "chr2".to_string(), "chr1".to_string()];
let err = HeaderIndex::build(&headers).unwrap_err();
assert!(
matches!(&err, FmIndexError::DuplicateHeader(h) if h == "chr1"),
"unexpected error: {err}"
);
}
#[test]
fn header_index_of_empty_header_list_is_empty() {
let index = HeaderIndex::build(&[]).unwrap();
assert_eq!(index.get("anything"), None);
}
}