concept_graph/
identifiers.rs1use std::io::{self, Read, Write};
10
11use crate::ordinal::{Ordinal, to_usize};
12
13const MAGIC: &[u8; 8] = b"FTIDENT\0";
14const VERSION: u32 = 1;
15
16#[derive(Debug, thiserror::Error)]
18pub enum IdentifiersError {
19 #[error("identifiers I/O failed")]
21 Io(#[from] io::Error),
22 #[error("not an identifiers artifact")]
24 Magic,
25 #[error("identifiers layout version {found}, expected {expected}")]
27 Version {
28 found: u32,
30 expected: u32,
32 },
33 #[error("too many identifiers")]
35 TooMany(#[source] std::num::TryFromIntError),
36 #[error("an alternate identifier is not UTF-8")]
38 Text(#[from] std::string::FromUtf8Error),
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Default)]
43pub struct Identifiers {
44 entries: Vec<(u64, String, u32)>,
45}
46
47impl Identifiers {
48 #[must_use]
50 pub fn new(mut entries: Vec<(u64, String, Ordinal)>) -> Self {
51 entries.sort();
52 entries.dedup();
53 Self {
54 entries: entries
55 .into_iter()
56 .map(|(scheme, code, concept)| (scheme, code, concept.index()))
57 .collect(),
58 }
59 }
60
61 #[must_use]
64 pub fn size_in_bytes(&self) -> usize {
65 self.entries
66 .iter()
67 .fold(crate::footprint::vector(&self.entries), |total, entry| {
68 total.saturating_add(entry.1.capacity())
69 })
70 }
71
72 #[must_use]
74 pub fn lookup(&self, scheme: u64, code: &str) -> Option<Ordinal> {
75 self.entries
76 .binary_search_by(|(s, c, _)| (*s, c.as_str()).cmp(&(scheme, code)))
77 .ok()
78 .and_then(|i| self.entries.get(i))
79 .map(|(_, _, concept)| Ordinal::new(*concept))
80 }
81
82 #[must_use]
84 pub fn schemes(&self) -> Vec<u64> {
85 let mut schemes: Vec<u64> = self.entries.iter().map(|(s, _, _)| *s).collect();
86 schemes.dedup();
87 schemes
88 }
89
90 #[must_use]
92 pub fn len(&self) -> usize {
93 self.entries.len()
94 }
95
96 #[must_use]
98 pub fn is_empty(&self) -> bool {
99 self.entries.is_empty()
100 }
101
102 pub fn write_to(&self, out: &mut impl Write) -> Result<(), IdentifiersError> {
108 out.write_all(MAGIC)?;
109 out.write_all(&VERSION.to_le_bytes())?;
110 let count = u32::try_from(self.entries.len()).map_err(IdentifiersError::TooMany)?;
111 out.write_all(&count.to_le_bytes())?;
112 for (scheme, code, concept) in &self.entries {
113 out.write_all(&scheme.to_le_bytes())?;
114 let len = u32::try_from(code.len()).map_err(IdentifiersError::TooMany)?;
115 out.write_all(&len.to_le_bytes())?;
116 out.write_all(code.as_bytes())?;
117 out.write_all(&concept.to_le_bytes())?;
118 }
119 Ok(())
120 }
121
122 pub fn read_from(input: &mut impl Read) -> Result<Self, IdentifiersError> {
128 let mut magic = [0_u8; 8];
129 input.read_exact(&mut magic)?;
130 if &magic != MAGIC {
131 return Err(IdentifiersError::Magic);
132 }
133 let version = read_u32(input)?;
134 if version != VERSION {
135 return Err(IdentifiersError::Version {
136 found: version,
137 expected: VERSION,
138 });
139 }
140 let count = read_u32(input)?;
141 let mut entries = Vec::with_capacity(to_usize(count));
142 for _ in 0..count {
143 let mut long = [0_u8; 8];
144 input.read_exact(&mut long)?;
145 let len = to_usize(read_u32(input)?);
146 let mut bytes = vec![0_u8; len];
147 input.read_exact(&mut bytes)?;
148 let concept = read_u32(input)?;
149 entries.push((u64::from_le_bytes(long), String::from_utf8(bytes)?, concept));
150 }
151 entries.sort();
152 Ok(Self { entries })
153 }
154}
155
156fn read_u32(input: &mut impl Read) -> Result<u32, IdentifiersError> {
157 let mut bytes = [0_u8; 4];
158 input.read_exact(&mut bytes)?;
159 Ok(u32::from_le_bytes(bytes))
160}
161
162#[cfg(test)]
163mod tests {
164 use super::{Identifiers, IdentifiersError};
165 use crate::ordinal::Ordinal;
166
167 #[test]
168 fn codes_resolve_per_scheme_and_the_layout_round_trips() {
169 let identifiers = Identifiers::new(vec![
170 (705_114_005, String::from("54486-6"), Ordinal::new(3)),
171 (705_114_005, String::from("1234-5"), Ordinal::new(4)),
172 (900, String::from("54486-6"), Ordinal::new(5)),
173 ]);
174 assert_eq!(
175 identifiers.lookup(705_114_005, "54486-6"),
176 Some(Ordinal::new(3))
177 );
178 assert_eq!(identifiers.lookup(900, "54486-6"), Some(Ordinal::new(5)));
179 assert_eq!(identifiers.lookup(705_114_005, "9999"), None);
180 assert_eq!(identifiers.schemes(), [900, 705_114_005]);
181 let mut bytes = Vec::new();
182 identifiers.write_to(&mut bytes).expect("writes");
183 assert_eq!(
184 Identifiers::read_from(&mut bytes.as_slice()).expect("reads"),
185 identifiers
186 );
187 assert!(matches!(
188 Identifiers::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
189 Err(IdentifiersError::Magic)
190 ));
191 }
192}