1use anyhow::{ensure, Result};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::path::Path;
6
7#[derive(Clone, Debug, Deserialize, Serialize)]
8#[serde(default, deny_unknown_fields)]
9pub struct QueryConfig {
10 #[serde(with = "id_map")]
11 pub identifier_schemes: BTreeMap<String, u64>,
12 #[serde(with = "id_map")]
13 pub dialects: BTreeMap<String, u64>,
14 pub member_language: String,
15}
16impl Default for QueryConfig {
17 fn default() -> Self {
18 Self {
19 member_language: "en".into(),
20 identifier_schemes: BTreeMap::new(),
21 dialects: BTreeMap::from([
22 ("da-dk".into(), 554461000005103),
23 ("en-au".into(), 32570271000036106),
24 ("en-ca".into(), 19491000087109),
25 ("en-gb".into(), 900000000000508004),
26 ("en-ie".into(), 21000220103),
27 ("en-nz".into(), 271000210107),
28 ("en-nz-x-pat".into(), 281000210109),
29 ("en-us".into(), 900000000000509007),
30 ("en-x-gmdn".into(), 608771002),
31 ("en-x-nhs-clinical".into(), 999001261000000100),
32 ("en-x-nhs-dmd".into(), 999000671000001103),
33 ("en-x-nhs-pharmacy".into(), 999000691000001104),
34 ("en-gb-x-drug".into(), 999000681000001101),
35 ("en-gb-x-ext".into(), 999001251000000103),
36 ("es".into(), 450828004),
37 ("es-uy".into(), 5641000179103),
38 ("et-ee".into(), 71000181105),
39 ("de".into(), 722130004),
40 ("fr".into(), 722131000),
41 ("fr-be".into(), 21000172104),
42 ("fr-ca".into(), 20581000087109),
43 ("ja".into(), 722129009),
44 ("mi".into(), 291000210106),
45 ("nl-be".into(), 31000172101),
46 ("nl-nl".into(), 31000146106),
47 ("nb-no".into(), 61000202103),
48 ("nn-no".into(), 91000202106),
49 ("sv-se".into(), 46011000052107),
50 ("zh".into(), 722128001),
51 ]),
52 }
53 }
54}
55impl QueryConfig {
56 pub fn fingerprint(&self) -> Result<String> {
57 use sha2::{Digest, Sha256};
58 Ok(format!("{:x}", Sha256::digest(serde_json::to_vec(self)?)))
59 }
60 pub fn read(path: &Path) -> Result<Self> {
61 let mut config: Self = serde_json::from_reader(std::fs::File::open(path)?)?;
62 config.normalise()?;
63 Ok(config)
64 }
65 pub fn normalise(&mut self) -> Result<()> {
66 ensure!(
67 self.member_language.len() == 2
68 && self
69 .member_language
70 .bytes()
71 .all(|b| b.is_ascii_alphabetic()),
72 "Member language must be a two-letter language code"
73 );
74 self.member_language.make_ascii_lowercase();
75 for map in [&mut self.identifier_schemes, &mut self.dialects] {
76 let mut result = BTreeMap::new();
77 for (name, id) in map.iter() {
78 ensure!(valid_alias(name), "Invalid alias name");
79 ensure!(
80 (100000..1_000_000_000_000_000_000).contains(id),
81 "Invalid alias SCTID"
82 );
83 ensure!(
84 result.insert(name.to_ascii_lowercase(), *id).is_none(),
85 "Duplicate alias ignoring case"
86 );
87 }
88 *map = result;
89 }
90 Ok(())
91 }
92 pub fn member_language_code(&self) -> Option<[u8; 2]> {
93 self.member_language
94 .as_bytes()
95 .try_into()
96 .ok()
97 .filter(|code: &[u8; 2]| code.iter().all(u8::is_ascii_lowercase))
98 }
99}
100pub(crate) fn valid_alias(name: &str) -> bool {
101 name.as_bytes().first().is_some_and(u8::is_ascii_alphabetic)
102 && name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-')
103}
104mod id_map {
105 use super::*;
106 pub fn serialize<S: serde::Serializer>(
107 map: &BTreeMap<String, u64>,
108 serializer: S,
109 ) -> Result<S::Ok, S::Error> {
110 map.iter()
111 .map(|(k, v)| (k, v.to_string()))
112 .collect::<BTreeMap<_, _>>()
113 .serialize(serializer)
114 }
115 pub fn deserialize<'de, D: serde::Deserializer<'de>>(
116 deserializer: D,
117 ) -> Result<BTreeMap<String, u64>, D::Error> {
118 let values = BTreeMap::<String, String>::deserialize(deserializer)?;
119 values
120 .into_iter()
121 .map(|(k, v)| {
122 if v.is_empty() || !v.bytes().all(|b| b.is_ascii_digit()) {
123 return Err(serde::de::Error::custom("SCTIDs must be decimal strings"));
124 }
125 v.parse()
126 .map(|id| (k, id))
127 .map_err(serde::de::Error::custom)
128 })
129 .collect()
130 }
131}