snomed_ecl_engine/store/
identifiers.rs1use super::{sha256, IndexSource, Section};
2use anyhow::{bail, ensure, Result};
3use serde::{Deserialize, Serialize};
4use std::io::{BufWriter, Write};
5use std::path::Path;
6use std::sync::OnceLock;
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
9pub struct Identifier {
10 pub scheme: u64,
11 pub code: String,
12 pub referenced_component: u64,
13 pub module: u64,
14 pub effective_time: u32,
15 pub active: bool,
16}
17#[derive(Clone, Debug, Default, Serialize, Deserialize)]
18pub struct IdentifierIndex {
19 pub rows: Vec<Identifier>,
20}
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct IdentifierManifest {
23 pub bytes: u64,
24 pub sha256: String,
25 pub rows: usize,
26}
27impl IdentifierIndex {
28 pub fn build(mut rows: Vec<Identifier>) -> Result<Self> {
29 rows.sort_unstable_by(|a, b| (a.scheme, &a.code).cmp(&(b.scheme, &b.code)));
30 let result = Self { rows };
31 result.validate()?;
32 Ok(result)
33 }
34 pub fn validate(&self) -> Result<()> {
35 ensure!(
36 self.rows
37 .windows(2)
38 .all(|w| (w[0].scheme, &w[0].code) < (w[1].scheme, &w[1].code)),
39 "Duplicate or unsorted alternate identifiers"
40 );
41 for row in &self.rows {
42 ensure!(
43 !row.code.is_empty() && !row.code.chars().any(|c| c.is_ascii_control()),
44 "Invalid alternate identifier code"
45 );
46 ensure!(
47 [row.scheme, row.module, row.referenced_component]
48 .iter()
49 .all(|id| (100000..1_000_000_000_000_000_000).contains(id)),
50 "Invalid identifier SCTID"
51 );
52 ensure!(
53 super::members::valid_time(row.effective_time),
54 "Invalid identifier effective time"
55 );
56 }
57 Ok(())
58 }
59 pub fn lookup(&self, scheme: u64, code: &str) -> Option<u64> {
60 let pos = self
61 .rows
62 .binary_search_by(|r| (r.scheme, r.code.as_str()).cmp(&(scheme, code)))
63 .ok()?;
64 let row = &self.rows[pos];
65 (row.active && matches!((row.referenced_component / 10) % 100, 0 | 10))
66 .then_some(row.referenced_component)
67 }
68 pub fn write(&self, directory: &Path) -> Result<IdentifierManifest> {
69 self.validate()?;
70 let path = directory.join("identifiers.json");
71 let mut file = BufWriter::new(std::fs::File::create_new(&path)?);
72 serde_json::to_writer(&mut file, self)?;
73 file.flush()?;
74 file.get_ref().sync_all()?;
75 Ok(IdentifierManifest {
76 bytes: path.metadata()?.len(),
77 sha256: sha256(&path)?,
78 rows: self.rows.len(),
79 })
80 }
81 pub(super) fn open(section: &Section, manifest: &IdentifierManifest) -> Result<Self> {
82 section.verify()?;
83 let index: Self = serde_json::from_reader(std::io::BufReader::new(section.reader()?))?;
84 index.validate()?;
85 ensure!(
86 index.rows.len() == manifest.rows,
87 "Identifier count differs from manifest"
88 );
89 Ok(index)
90 }
91}
92#[derive(Debug, Default)]
93pub struct IdentifierStore {
94 source: Option<(Section, IdentifierManifest)>,
95 loaded: OnceLock<std::result::Result<IdentifierIndex, String>>,
96}
97impl IdentifierStore {
98 pub fn loaded(index: IdentifierIndex) -> Result<Self> {
99 index.validate()?;
100 Ok(Self {
101 source: None,
102 loaded: OnceLock::from(Ok(index)),
103 })
104 }
105 pub(super) fn lazy(source: &IndexSource, manifest: IdentifierManifest) -> Result<Self> {
106 Ok(Self {
107 source: Some((source.section("identifiers.json")?, manifest)),
108 loaded: OnceLock::new(),
109 })
110 }
111 pub fn get(&self) -> Result<Option<&IdentifierIndex>> {
112 if self.source.is_none() && self.loaded.get().is_none() {
113 return Ok(None);
114 }
115 match self.loaded.get_or_init(|| {
116 let (path, manifest) = self.source.as_ref().unwrap();
117 IdentifierIndex::open(path, manifest).map_err(|e| e.to_string())
118 }) {
119 Ok(index) => Ok(Some(index)),
120 Err(e) => bail!("Identifier index: {e}"),
121 }
122 }
123}