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,
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]
63 pub fn lookup(&self, scheme: u64, code: &str) -> Option<Ordinal> {
64 self.entries
65 .binary_search_by(|(s, c, _)| (*s, c.as_str()).cmp(&(scheme, code)))
66 .ok()
67 .and_then(|i| self.entries.get(i))
68 .map(|(_, _, concept)| Ordinal::new(*concept))
69 }
70
71 #[must_use]
73 pub fn schemes(&self) -> Vec<u64> {
74 let mut schemes: Vec<u64> = self.entries.iter().map(|(s, _, _)| *s).collect();
75 schemes.dedup();
76 schemes
77 }
78
79 #[must_use]
81 pub fn len(&self) -> usize {
82 self.entries.len()
83 }
84
85 #[must_use]
87 pub fn is_empty(&self) -> bool {
88 self.entries.is_empty()
89 }
90
91 pub fn write_to(&self, out: &mut impl Write) -> Result<(), IdentifiersError> {
97 out.write_all(MAGIC)?;
98 out.write_all(&VERSION.to_le_bytes())?;
99 let count = u32::try_from(self.entries.len()).map_err(|_| IdentifiersError::TooMany)?;
100 out.write_all(&count.to_le_bytes())?;
101 for (scheme, code, concept) in &self.entries {
102 out.write_all(&scheme.to_le_bytes())?;
103 let len = u32::try_from(code.len()).map_err(|_| IdentifiersError::TooMany)?;
104 out.write_all(&len.to_le_bytes())?;
105 out.write_all(code.as_bytes())?;
106 out.write_all(&concept.to_le_bytes())?;
107 }
108 Ok(())
109 }
110
111 pub fn read_from(input: &mut impl Read) -> Result<Self, IdentifiersError> {
117 let mut magic = [0_u8; 8];
118 input.read_exact(&mut magic)?;
119 if &magic != MAGIC {
120 return Err(IdentifiersError::Magic);
121 }
122 let version = read_u32(input)?;
123 if version != VERSION {
124 return Err(IdentifiersError::Version {
125 found: version,
126 expected: VERSION,
127 });
128 }
129 let count = read_u32(input)?;
130 let mut entries = Vec::with_capacity(to_usize(count));
131 for _ in 0..count {
132 let mut long = [0_u8; 8];
133 input.read_exact(&mut long)?;
134 let len = to_usize(read_u32(input)?);
135 let mut bytes = vec![0_u8; len];
136 input.read_exact(&mut bytes)?;
137 let concept = read_u32(input)?;
138 entries.push((u64::from_le_bytes(long), String::from_utf8(bytes)?, concept));
139 }
140 entries.sort();
141 Ok(Self { entries })
142 }
143}
144
145fn read_u32(input: &mut impl Read) -> Result<u32, IdentifiersError> {
146 let mut bytes = [0_u8; 4];
147 input.read_exact(&mut bytes)?;
148 Ok(u32::from_le_bytes(bytes))
149}
150
151#[cfg(test)]
152mod tests {
153 use super::{Identifiers, IdentifiersError};
154 use crate::ordinal::Ordinal;
155
156 #[test]
157 fn codes_resolve_per_scheme_and_the_layout_round_trips() {
158 let identifiers = Identifiers::new(vec![
159 (705_114_005, String::from("54486-6"), Ordinal::new(3)),
160 (705_114_005, String::from("1234-5"), Ordinal::new(4)),
161 (900, String::from("54486-6"), Ordinal::new(5)),
162 ]);
163 assert_eq!(
164 identifiers.lookup(705_114_005, "54486-6"),
165 Some(Ordinal::new(3))
166 );
167 assert_eq!(identifiers.lookup(900, "54486-6"), Some(Ordinal::new(5)));
168 assert_eq!(identifiers.lookup(705_114_005, "9999"), None);
169 assert_eq!(identifiers.schemes(), [900, 705_114_005]);
170 let mut bytes = Vec::new();
171 identifiers.write_to(&mut bytes).expect("writes");
172 assert_eq!(
173 Identifiers::read_from(&mut bytes.as_slice()).expect("reads"),
174 identifiers
175 );
176 assert!(matches!(
177 Identifiers::read_from(&mut b"XXXXXXXX\0\0\0\0".as_slice()),
178 Err(IdentifiersError::Magic)
179 ));
180 }
181}