1use uqa_analysis::TokenTerm;
10
11mod allocation;
12
13use crate::{StorageBackendError, StorageBackendResult};
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct TokenTermKey(Vec<u8>);
20
21impl TokenTermKey {
22 pub fn from_text(text: &str) -> Self {
23 let mut bytes = Vec::with_capacity(text.len() + 1);
24 bytes.push(0);
25 bytes.extend_from_slice(text.as_bytes());
26 Self(bytes)
27 }
28
29 pub fn from_term(term: &TokenTerm) -> Self {
30 if let Some(text) = term.as_str() {
31 return Self::from_text(text);
32 }
33 let units = term.utf16();
34 let mut bytes = Vec::with_capacity(units.len() * 2 + 1);
35 bytes.push(1);
36 for unit in units.iter() {
37 bytes.extend_from_slice(&unit.to_be_bytes());
38 }
39 Self(bytes)
40 }
41
42 pub fn from_bytes(bytes: Vec<u8>) -> StorageBackendResult<Self> {
43 Self::validate(&bytes)?;
44 Ok(Self(bytes))
45 }
46
47 pub fn as_str(&self) -> Option<&str> {
49 (self.0[0] == 0).then(|| std::str::from_utf8(&self.0[1..]).expect("validated UTF-8 key"))
50 }
51
52 pub fn as_bytes(&self) -> &[u8] {
53 &self.0
54 }
55
56 pub(crate) fn allocated_bytes(&self) -> usize {
57 self.0.capacity()
58 }
59
60 pub fn into_bytes(self) -> Vec<u8> {
61 self.0
62 }
63
64 pub fn to_term(&self) -> TokenTerm {
65 match self.0[0] {
66 0 => TokenTerm::from(std::str::from_utf8(&self.0[1..]).expect("validated UTF-8 key")),
67 1 => TokenTerm::from_utf16(
68 self.0[1..]
69 .as_chunks::<2>()
70 .0
71 .iter()
72 .map(|pair| u16::from_be_bytes([pair[0], pair[1]]))
73 .collect(),
74 ),
75 _ => unreachable!("validated token key tag"),
76 }
77 }
78
79 fn validate(bytes: &[u8]) -> StorageBackendResult<()> {
80 let invalid = || StorageBackendError::Other("invalid canonical token term key".into());
81 match bytes.split_first() {
82 Some((0, bytes)) => std::str::from_utf8(bytes)
83 .map(|_| ())
84 .map_err(|_| invalid()),
85 Some((1, bytes)) if bytes.len() % 2 == 0 => {
86 let units = bytes
87 .as_chunks::<2>()
88 .0
89 .iter()
90 .map(|pair| u16::from_be_bytes([pair[0], pair[1]]));
91 if !char::decode_utf16(units).any(|unit| unit.is_err()) {
92 return Err(invalid());
93 }
94 Ok(())
95 }
96 _ => Err(invalid()),
97 }
98 }
99}
100
101impl From<String> for TokenTermKey {
102 fn from(text: String) -> Self {
103 Self::from_text(&text)
104 }
105}
106
107impl From<&str> for TokenTermKey {
108 fn from(text: &str) -> Self {
109 Self::from_text(text)
110 }
111}