Skip to main content

concept_store/
keys.rs

1//! A sorted `u64` key to `u32` ordinal table beside an artifact.
2//!
3//! A code system whose concepts have identifiers beyond their codes (the atom
4//! identifiers of `RxNorm`, the entity identifiers of ICD-11) keeps them here,
5//! each next to its concept ordinal. No spec governs the layout: our own
6//! design, little-endian, a magic and version, a count, then sorted
7//! `(key, ordinal)` pairs.
8
9use std::io::{self, Read, Write};
10
11const MAGIC: &[u8; 8] = b"FTKEYS\0\0";
12const VERSION: u32 = 1;
13
14/// A count past the integer the key table layout stores.
15#[derive(Debug, thiserror::Error)]
16#[error("{what} exceeds the integer the key table layout stores")]
17struct Overflow {
18    what: &'static str,
19    #[source]
20    source: std::num::TryFromIntError,
21}
22
23/// A failure while reading or writing the table.
24#[derive(Debug, thiserror::Error)]
25pub enum KeyTableError {
26    /// An I/O failure.
27    #[error("key table I/O failed")]
28    Io(#[from] io::Error),
29    /// The bytes do not start with the table magic.
30    #[error("not a key table")]
31    Magic,
32    /// The layout version is not the one this build reads.
33    #[error("key table version {found}, expected {expected}")]
34    Version {
35        /// The version found.
36        found: u32,
37        /// The version this build reads.
38        expected: u32,
39    },
40}
41
42/// The sorted table.
43#[derive(Debug, Clone, PartialEq, Eq, Default)]
44pub struct KeyTable {
45    pairs: Vec<(u64, u32)>,
46}
47
48impl KeyTable {
49    /// Builds the table from `(key, ordinal)` pairs, in any order.
50    #[must_use]
51    pub fn new(mut pairs: Vec<(u64, u32)>) -> Self {
52        pairs.sort_unstable();
53        pairs.dedup();
54        Self { pairs }
55    }
56
57    /// The ordinal stored under `key`.
58    #[must_use]
59    pub fn get(&self, key: u64) -> Option<u32> {
60        self.pairs
61            .binary_search_by_key(&key, |(k, _)| *k)
62            .ok()
63            .and_then(|i| self.pairs.get(i))
64            .map(|(_, o)| *o)
65    }
66
67    /// The entry count.
68    #[must_use]
69    pub fn len(&self) -> usize {
70        self.pairs.len()
71    }
72
73    /// Whether the table is empty.
74    #[must_use]
75    pub fn is_empty(&self) -> bool {
76        self.pairs.is_empty()
77    }
78
79    /// Writes the layout.
80    ///
81    /// # Errors
82    ///
83    /// Returns [`KeyTableError::Io`] when writing fails.
84    pub fn write_to(&self, out: &mut impl Write) -> Result<(), KeyTableError> {
85        out.write_all(MAGIC)?;
86        out.write_all(&VERSION.to_le_bytes())?;
87        let len = u64::try_from(self.pairs.len()).map_err(|source| {
88            io::Error::other(Overflow {
89                what: "the key count",
90                source,
91            })
92        })?;
93        out.write_all(&len.to_le_bytes())?;
94        for (key, ordinal) in &self.pairs {
95            out.write_all(&key.to_le_bytes())?;
96            out.write_all(&ordinal.to_le_bytes())?;
97        }
98        Ok(())
99    }
100
101    /// Reads the layout.
102    ///
103    /// # Errors
104    ///
105    /// Returns [`KeyTableError`] for a truncated or foreign table.
106    pub fn read_from(input: &mut impl Read) -> Result<Self, KeyTableError> {
107        let mut magic = [0_u8; 8];
108        input.read_exact(&mut magic)?;
109        if &magic != MAGIC {
110            return Err(KeyTableError::Magic);
111        }
112        let mut word = [0_u8; 4];
113        input.read_exact(&mut word)?;
114        let version = u32::from_le_bytes(word);
115        if version != VERSION {
116            return Err(KeyTableError::Version {
117                found: version,
118                expected: VERSION,
119            });
120        }
121        let mut long = [0_u8; 8];
122        input.read_exact(&mut long)?;
123        let len = usize::try_from(u64::from_le_bytes(long)).map_err(|source| {
124            io::Error::other(Overflow {
125                what: "the key count",
126                source,
127            })
128        })?;
129        let mut pairs = Vec::with_capacity(len);
130        for _ in 0..len {
131            input.read_exact(&mut long)?;
132            input.read_exact(&mut word)?;
133            pairs.push((u64::from_le_bytes(long), u32::from_le_bytes(word)));
134        }
135        Ok(Self { pairs })
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::{KeyTable, KeyTableError};
142
143    #[test]
144    fn the_table_answers_by_key_and_round_trips() {
145        let keys = KeyTable::new(vec![(829, 0), (12_251_526, 1), (2_798_745, 1), (829, 0)]);
146        assert_eq!(keys.len(), 3);
147        assert_eq!(keys.get(2_798_745), Some(1));
148        assert_eq!(keys.get(1), None);
149        let mut bytes = Vec::new();
150        keys.write_to(&mut bytes).expect("writes");
151        assert_eq!(
152            KeyTable::read_from(&mut bytes.as_slice()).expect("reads"),
153            keys
154        );
155        assert!(matches!(
156            KeyTable::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
157            Err(KeyTableError::Magic)
158        ));
159    }
160}