1use std::collections::HashMap;
7use std::io;
8
9use crate::q42_lex::{LexError, Q42LexMmap};
10use crate::q42_volume::Q42Volume;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum CborLdError {
15 InvalidCbor,
16 InvalidValueType,
17 MissingField,
18 UnsupportedFeature,
19 InvalidOffset,
20 InvalidUtf8,
21}
22
23#[derive(Debug, Clone)]
25pub struct SemanticPayload {
26 pub data: Vec<u8>,
27 pub context: Q42Context,
28 pub semantic_context: HashMap<String, String>,
29 pub did_q42: Option<String>,
30 pub wireguard_pubkey: Option<String>,
31 pub routing_constraints: Vec<String>,
32 pub peer_capabilities: HashMap<String, String>,
33}
34
35#[derive(Debug, Clone)]
37pub struct Q42Context {
38 pub base_iri: String,
39 pub vocabulary: HashMap<String, String>,
40}
41
42impl Q42Context {
43 pub fn new() -> Self {
44 let mut vocabulary = HashMap::new();
45 vocabulary.insert(
46 "rdf".to_string(),
47 "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
48 );
49 vocabulary.insert(
54 "rdfs".to_string(),
55 "http://www.w3.org/2000/01/rdf-schema#".to_string(),
56 );
57 vocabulary.insert("sh".to_string(), "http://www.w3.org/ns/shacl#".to_string());
58 vocabulary.insert(
59 "owl".to_string(),
60 "http://www.w3.org/2002/07/owl#".to_string(),
61 );
62 vocabulary.insert(
63 "xsd".to_string(),
64 "http://www.w3.org/2001/XMLSchema#".to_string(),
65 );
66 vocabulary.insert("hcai".to_string(), "http://www.w3.org/ns/hcai#".to_string());
67 vocabulary.insert(
68 "qualia".to_string(),
69 "https://webizen.org/ld/vocab/".to_string(),
70 );
71
72 Self {
73 base_iri: "https://webizen.org/ld/context/v1".to_string(),
74 vocabulary,
75 }
76 }
77
78 pub fn from_volume(_volume: &Q42Volume) -> Result<Self, io::Error> {
79 Ok(Self::new())
80 }
81
82 pub fn resolve_semantic_term(&self, term: &str) -> Option<String> {
83 self.vocabulary.get(term).cloned()
84 }
85
86 pub fn context_hash(&self) -> u64 {
89 let mut entries: Vec<(&String, &String)> = self.vocabulary.iter().collect();
90 entries.sort_by_key(|(k, _)| *k);
91 let mut combined = self.base_iri.clone();
92 for (k, v) in entries {
93 combined.push_str(k);
94 combined.push_str(v);
95 }
96 crate::q_hash(&combined)
97 }
98
99 pub fn expand_to_hash(&self, compact_iri: &str) -> u64 {
102 let expanded = if let Some(colon) = compact_iri.find(':') {
103 let (prefix, suffix) = (&compact_iri[..colon], &compact_iri[colon + 1..]);
104 match self.vocabulary.get(prefix) {
105 Some(base) => format!("{base}{suffix}"),
106 None => compact_iri.to_string(),
107 }
108 } else {
109 compact_iri.to_string()
110 };
111 crate::q_hash(&expanded)
112 }
113}
114
115impl Default for Q42Context {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct Q42CborLdParser {
124 lexicon: Q42Lexicon,
125}
126
127impl Q42CborLdParser {
128 pub fn new(lexicon: Q42Lexicon) -> Self {
129 Self { lexicon }
130 }
131
132 pub fn from_volume(volume: &Q42Volume) -> Result<Self, io::Error> {
133 let lexicon = Q42Lexicon::from_volume(volume).map_err(|_| {
134 io::Error::new(
135 io::ErrorKind::InvalidData,
136 "Failed to load lexicon from volume",
137 )
138 })?;
139 Ok(Self::new(lexicon))
140 }
141
142 pub fn lexicon(&self) -> &Q42Lexicon {
145 &self.lexicon
146 }
147
148 pub fn parse(&self, data: &[u8]) -> Result<SemanticPayload, CborLdError> {
149 Ok(SemanticPayload {
150 data: data.to_vec(),
151 context: Q42Context::new(),
152 semantic_context: HashMap::new(),
153 did_q42: None,
154 wireguard_pubkey: None,
155 routing_constraints: Vec::new(),
156 peer_capabilities: HashMap::new(),
157 })
158 }
159
160 pub fn parse_semantic_payload(&self, data: &[u8]) -> Result<SemanticPayload, CborLdError> {
161 self.parse(data)
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct Q42Lexicon {
168 pub terms: HashMap<String, u64>,
170 pub reverse: HashMap<u64, String>,
172 pub version: LexiconVersion,
174 pub context_uri: String,
176 pub vocabulary: HashMap<String, String>,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum LexiconVersion {
182 V2,
183}
184
185impl LexiconVersion {
186 pub fn v2() -> Self {
187 LexiconVersion::V2
188 }
189}
190
191impl Q42Lexicon {
192 pub fn new() -> Self {
194 let mut vocabulary = HashMap::new();
195 vocabulary.insert(
196 "rdf".to_string(),
197 "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string(),
198 );
199 vocabulary.insert(
203 "rdfs".to_string(),
204 "http://www.w3.org/2000/01/rdf-schema#".to_string(),
205 );
206 vocabulary.insert("sh".to_string(), "http://www.w3.org/ns/shacl#".to_string());
207 vocabulary.insert(
208 "owl".to_string(),
209 "http://www.w3.org/2002/07/owl#".to_string(),
210 );
211 vocabulary.insert(
212 "xsd".to_string(),
213 "http://www.w3.org/2001/XMLSchema#".to_string(),
214 );
215 vocabulary.insert("hcai".to_string(), "http://www.w3.org/ns/hcai#".to_string());
216 vocabulary.insert(
217 "qualia".to_string(),
218 "https://webizen.org/ld/vocab/".to_string(),
219 );
220 vocabulary.insert(
221 "did".to_string(),
222 "https://www.w3.org/TR/did-core/".to_string(),
223 );
224 vocabulary.insert("sec".to_string(), "https://w3id.org/security/".to_string());
225
226 Self {
227 terms: HashMap::new(),
228 reverse: HashMap::new(),
229 version: LexiconVersion::v2(),
230 context_uri: "https://webizen.org/ld/context/v1".to_string(),
231 vocabulary,
232 }
233 }
234
235 pub fn from_volume(volume: &Q42Volume) -> Result<Self, LexError> {
237 let lex_data = volume.lex_bytes();
238 let lex_view = Q42LexMmap::from_bytes(lex_data)?;
239
240 let mut terms = HashMap::new();
241 let mut reverse = HashMap::new();
242
243 for i in 0..lex_view.entry_count() {
246 let Some(hash) = lex_view.hash_at(i) else {
247 break;
248 };
249 if let Some(text) = lex_view
250 .lookup_webizen_identity(hash)
251 .or_else(|| lex_view.lookup_hash(hash))
252 {
253 terms.insert(text.to_string(), hash);
254 reverse.insert(hash, text.to_string());
255 }
256 }
257
258 let vocabulary = Self::new().vocabulary;
259
260 Ok(Self {
261 terms,
262 reverse,
263 version: LexiconVersion::v2(),
264 context_uri: "https://webizen.org/ld/context/v1".to_string(),
265 vocabulary,
266 })
267 }
268
269 pub fn resolve_term(&self, term: &str) -> Option<u64> {
271 self.terms.get(term).copied()
272 }
273
274 pub fn resolve_hash(&self, hash: u64) -> Option<&str> {
276 self.reverse.get(&hash).map(|s| s.as_str())
277 }
278
279 pub fn contains_term(&self, term: &str) -> bool {
281 self.terms.contains_key(term)
282 }
283
284 pub fn contains_hash(&self, hash: u64) -> bool {
286 self.reverse.contains_key(&hash)
287 }
288
289 pub fn add_term(&mut self, term: String, hash: u64) {
291 self.terms.insert(term.clone(), hash);
292 self.reverse.insert(hash, term);
293 }
294
295 pub fn expand_iri(&self, compact_iri: &str) -> Option<String> {
297 if let Some(colon_pos) = compact_iri.find(':') {
298 let prefix = &compact_iri[..colon_pos];
299 let suffix = &compact_iri[colon_pos + 1..];
300 self.vocabulary
301 .get(prefix)
302 .map(|base| format!("{}{}", base, suffix))
303 } else {
304 None
305 }
306 }
307
308 pub fn vocabulary(&self) -> &HashMap<String, String> {
310 &self.vocabulary
311 }
312}
313
314impl Default for Q42Lexicon {
315 fn default() -> Self {
316 Self::new()
317 }
318}