1use super::Facade;
2use crate::data_storage::Namespace;
3#[cfg(feature = "local-references")]
4use crate::local_references;
5use crate::{
6 data_storage::DataStorage,
7 repositories::{OCABundleCacheRepo, OCABundleFTSRepo},
8};
9use oca_ast_semantics::ast::{self, OCAAst, ObjectKind, RefValue};
10use oca_bundle_semantics::build::OCABuildStep;
11use oca_bundle_semantics::state::oca::{capture_base::CaptureBase, DynOverlay, OCABundle};
12use said::{
13 derivation::HashFunctionCode,
14 sad::{SerializationFormats, SAD},
15 version::SerializationInfo,
16 SelfAddressingIdentifier,
17};
18
19use serde::Serialize;
20use std::borrow::Borrow;
21#[cfg(feature = "local-references")]
22use std::collections::HashMap;
23use std::str::FromStr;
24
25#[derive(Debug, Serialize)]
26#[serde(untagged)]
27pub enum OCAObject {
28 CaptureBase(CaptureBase),
29 Overlay(DynOverlay),
30}
31
32#[derive(Debug, Serialize)]
33pub struct SearchResult {
34 #[serde(rename = "r")]
35 pub records: Vec<SearchRecord>,
36 #[serde(rename = "m")]
37 pub metadata: SearchMetadata,
38}
39
40#[derive(Debug, Serialize)]
41pub struct SearchRecord {
42 pub oca_bundle: OCABundle,
43 pub metadata: SearchRecordMetadata,
44}
45
46#[derive(Debug, Serialize)]
47pub struct SearchRecordMetadata {
48 pub phrase: String,
49 pub scope: String,
50 pub score: f32,
51}
52
53#[derive(Debug, Serialize)]
54pub struct SearchMetadata {
55 pub total: usize,
56 pub page: usize,
57}
58
59#[derive(Debug, Serialize)]
60pub struct AllOCABundleResult {
61 pub records: Vec<OCABundle>,
62 pub metadata: AllOCABundleMetadata,
63}
64
65#[derive(Debug, Serialize)]
66pub struct AllOCABundleMetadata {
67 pub total: usize,
68 pub page: usize,
69}
70
71#[derive(Debug, Serialize)]
72pub struct AllCaptureBaseResult {
73 pub records: Vec<CaptureBase>,
74 pub metadata: AllCaptureBaseMetadata,
75}
76
77#[derive(SAD, Debug, Serialize)]
78#[version(protocol = "OCAA", major = 1, minor = 1)]
79pub struct BundleWithDependencies {
80 pub bundle: OCABundle,
81 pub dependencies: Vec<OCABundle>,
82}
83
84#[derive(Debug, Serialize)]
85pub struct AllCaptureBaseMetadata {
86 pub total: usize,
87 pub page: usize,
88}
89
90impl Facade {
91 pub fn search_oca_bundle(
92 &self,
93 language: Option<isolang::Language>,
94 query: String,
95 limit: usize,
96 page: usize,
97 ) -> SearchResult {
98 let oca_bundle_fts_repo = OCABundleFTSRepo::new(self.connection());
99 let search_result = oca_bundle_fts_repo.search(language, query, limit, page);
100 let records = search_result
101 .records
102 .iter()
103 .map(|record| SearchRecord {
104 oca_bundle: self
106 .get_oca_bundle(record.oca_bundle_said.clone(), false)
107 .unwrap()
108 .bundle
109 .clone(),
110 metadata: SearchRecordMetadata {
111 phrase: record.metadata.phrase.clone(),
112 scope: record.metadata.scope.clone(),
113 score: record.metadata.score,
114 },
115 })
116 .collect();
117 SearchResult {
118 records,
119 metadata: SearchMetadata {
120 total: search_result.metadata.total,
121 page: search_result.metadata.page,
122 },
123 }
124 }
125 #[cfg(feature = "local-references")]
126 pub fn fetch_all_refs(&self) -> Result<HashMap<String, String>, String> {
127 let mut refs: HashMap<String, String> = HashMap::new();
128 self.db
129 .get_all(Namespace::OCAReferences)
130 .unwrap()
131 .iter()
132 .for_each(|(k, v)| {
133 refs.insert(k.clone(), String::from_utf8(v.to_vec()).unwrap());
134 });
135 Ok(refs)
136 }
137
138 pub fn fetch_all_oca_bundle(
139 &self,
140 limit: usize,
141 page: usize,
142 ) -> Result<AllOCABundleResult, Vec<String>> {
143 let mut oca_bundles = vec![];
144 let mut total: usize = 0;
145 let mut errors = vec![];
146
147 let oca_bundle_cache_repo = OCABundleCacheRepo::new(self.connection());
148 let all_oca_bundle_records = oca_bundle_cache_repo.fetch_all(limit, page);
149 for all_oca_bundle_record in all_oca_bundle_records {
150 if total == 0 {
151 total = all_oca_bundle_record.total;
152 }
153 if let Some(cache_record) = all_oca_bundle_record.cache_record {
154 match serde_json::from_str(&cache_record.oca_bundle) {
155 Ok(oca_bundle) => {
156 oca_bundles.push(oca_bundle);
157 }
158 Err(e) => {
159 errors.push(format!("Failed to parse oca bundle: {}", e));
160 }
161 }
162 }
163 }
164 if !errors.is_empty() {
165 return Err(errors);
166 }
167
168 Ok(AllOCABundleResult {
169 records: oca_bundles,
170 metadata: AllOCABundleMetadata { total, page },
171 })
172 }
173
174 pub fn fetch_all_capture_base(
175 &self,
176 limit: usize,
177 page: usize,
178 ) -> Result<AllCaptureBaseResult, Vec<String>> {
179 let mut capture_bases = vec![];
180 let mut total: usize = 0;
181 let mut errors = vec![];
182
183 let capture_base_cache_repo =
184 crate::repositories::CaptureBaseCacheRepo::new(self.connection());
185 let all_capture_base_records = capture_base_cache_repo.fetch_all(limit, page);
186 for all_capture_base_record in all_capture_base_records {
187 if total == 0 {
188 total = all_capture_base_record.total;
189 }
190 if let Some(cache_record) = all_capture_base_record.cache_record {
191 match serde_json::from_str(&cache_record.capture_base) {
192 Ok(capture_base) => {
193 capture_bases.push(capture_base);
194 }
195 Err(e) => {
196 errors.push(format!("Failed to parse capture base: {}", e));
197 }
198 }
199 }
200 }
201 if !errors.is_empty() {
202 return Err(errors);
203 }
204
205 Ok(AllCaptureBaseResult {
206 records: capture_bases,
207 metadata: AllCaptureBaseMetadata { total, page },
208 })
209 }
210
211 pub fn get_oca_objects(&self, saids: Vec<String>) -> Result<Vec<OCAObject>, Vec<String>> {
212 let mut result: Vec<OCAObject> = vec![];
213 let mut errors: Vec<String> = vec![];
214
215 for said in saids {
216 let r = self
217 .db_cache
218 .get(Namespace::OCAObjectsJSON, &said)
219 .map_err(|e| {
220 errors.push(e.to_string());
221 errors.clone()
222 })?;
223 let object_str = String::from_utf8(r.ok_or_else(|| {
224 errors.push(format!("No OCA Object found for said: {}", said));
225 errors.clone()
226 })?)
227 .unwrap();
228 let r_type = self
229 .db
230 .get(Namespace::OCARelations, &format!("{}.metadata", said))
231 .map_err(|e| {
232 errors.push(e.to_string());
233 errors.clone()
234 })?;
235 let o_type: ObjectKind = (*r_type.unwrap().first().unwrap()).into();
236 match o_type {
237 ObjectKind::CaptureBase(_) => result.push(OCAObject::CaptureBase(
238 serde_json::from_str::<CaptureBase>(&object_str).map_err(|e| {
239 errors.push(format!("Failed to parse OCA object: {}", e));
240 errors.clone()
241 })?,
242 )),
243 ObjectKind::Overlay(_, _) => {
244 let oca_overlay = OCAObject::Overlay(
245 serde_json::from_str::<DynOverlay>(&object_str).map_err(|e| {
246 errors.push(format!("Failed to parse OCA object: {}", e));
247 errors.clone()
248 })?,
249 );
250 result.push(oca_overlay);
251 }
252 _ => {}
253 };
254 }
255
256 if !errors.is_empty() {
257 return Err(errors);
258 }
259
260 Ok(result)
261 }
262
263 pub fn get_oca_bundle(
264 &self,
265 said: SelfAddressingIdentifier,
266 with_dep: bool,
267 ) -> Result<BundleWithDependencies, Vec<String>> {
268 get_oca_bundle(self.db_cache.borrow(), said, with_dep)
269 }
270
271 pub fn get_oca_bundle_steps(
272 &self,
273 said: SelfAddressingIdentifier,
274 ) -> Result<Vec<OCABuildStep>, Vec<String>> {
275 let mut said = said.to_string();
276 #[allow(clippy::borrowed_box)]
277 fn extract_operation(
278 db: &Box<dyn DataStorage>,
279 said: &String,
280 ) -> Result<(String, oca_ast_semantics::ast::Command), Vec<String>> {
281 let r = db
282 .get(Namespace::OCA, &format!("oca.{}.operation", said))
283 .map_err(|e| vec![format!("{}", e)])?
284 .ok_or_else(|| vec![format!("No history found for said: {}", said)])?;
285
286 let said_length = r.first().unwrap();
287 let parent_said = String::from_utf8_lossy(&r[1..*said_length as usize + 1]).to_string();
288 let op_length = r.len() - *said_length as usize - 1;
289 let op = String::from_utf8_lossy(
290 &r[*said_length as usize + 1..*said_length as usize + 1 + op_length],
291 )
292 .to_string();
293
294 match serde_json::from_str::<oca_ast_semantics::ast::Command>(&op) {
295 Ok(command) => Ok((parent_said, command)),
296 Err(e) => Err(vec![format!("Failed to parse command: {}", e)]),
297 }
298 }
299
300 let mut history: Vec<OCABuildStep> = vec![];
301
302 loop {
303 let (parent_said, command) = extract_operation(&self.db, &said)?;
304 if parent_said == said {
305 dbg!("Malformed history for said: {}", said);
306 return Err(vec![format!("Malformed history")]);
307 }
308 let s = SelfAddressingIdentifier::from_str(&said).unwrap(); history.push(OCABuildStep {
310 parent_said: parent_said.clone().parse().ok(),
311 command,
312 result: self.get_oca_bundle(s, false).unwrap().bundle.clone(),
313 });
314 said = parent_said;
315
316 if said.is_empty() {
317 break;
318 }
319 }
320 history.reverse();
321 Ok(history)
322 }
323
324 pub fn get_oca_bundle_ocafile(
327 &self,
328 said: SelfAddressingIdentifier,
329 dereference: bool,
330 ) -> Result<String, Vec<String>> {
331 let oca_bundle_steps = self.get_oca_bundle_steps(said)?;
332 let mut oca_ast = OCAAst::new();
333 for step in oca_bundle_steps {
334 oca_ast.commands.push(step.command);
335 }
336
337 if dereference {
338 #[cfg(feature = "local-references")]
339 local_references::replace_refn_with_refs(&mut oca_ast, &self.db)
340 .map_err(|e| vec![e.to_string()])?;
341 }
342
343 Ok(oca_file_semantics::ocafile::generate_from_ast(&oca_ast))
344 }
345
346 pub fn get_oca_bundle_ast(
349 &self,
350 said: SelfAddressingIdentifier,
351 ) -> Result<OCAAst, Vec<String>> {
352 let oca_bundle_steps = self.get_oca_bundle_steps(said)?;
353 let mut oca_ast = OCAAst::new();
354 for step in oca_bundle_steps {
355 oca_ast.commands.push(step.command);
356 }
357 Ok(oca_ast)
358 }
359
360 pub fn parse_oca_bundle_to_ocafile(&self, bundle: &OCABundle) -> Result<String, Vec<String>> {
361 let oca_ast = bundle.to_ast();
362 Ok(oca_file_semantics::ocafile::generate_from_ast(&oca_ast))
363 }
364}
365
366pub fn get_oca_bundle(
367 storage: &dyn DataStorage,
368 said: SelfAddressingIdentifier,
369 with_dep: bool,
370) -> Result<BundleWithDependencies, Vec<String>> {
371 let r = storage
372 .get(Namespace::OCABundlesJSON, &said.to_string())
373 .map_err(|e| vec![format!("{}", e)])?;
374 let oca_bundle_str = String::from_utf8(
375 r.ok_or_else(|| vec![format!("No OCA Bundle found for said: {}", said)])?,
376 )
377 .unwrap();
378 let oca_bundle: Result<OCABundle, Vec<String>> = serde_json::from_str(&oca_bundle_str)
379 .map_err(|e| vec![format!("Failed to parse oca bundle: {}", e)]);
380
381 match oca_bundle {
382 Ok(oca_bundle) => {
383 let mut dep_bundles = vec![];
384 if with_dep {
385 for refs in retrive_all_references(oca_bundle.clone()) {
386 let dep_bundle = get_oca_bundle(storage, refs, true)?;
387 dep_bundles.push(dep_bundle.bundle);
388 dep_bundles.extend(dep_bundle.dependencies);
389 }
390 }
391 let result = BundleWithDependencies {
392 bundle: oca_bundle,
393 dependencies: dep_bundles,
394 };
395 Ok(result)
396 }
397 Err(e) => Err(e),
398 }
399}
400
401fn retrive_all_references(bundle: OCABundle) -> Vec<SelfAddressingIdentifier> {
409 let mut refs: Vec<SelfAddressingIdentifier> = vec![];
410
411 for (_, value) in bundle.capture_base.attributes {
412 match value {
413 ast::NestedAttrType::Reference(RefValue::Said(said)) => {
414 refs.push(said);
415 }
416 ast::NestedAttrType::Array(box_attr_type) => {
418 if let ast::NestedAttrType::Reference(RefValue::Said(said)) = &*box_attr_type {
419 refs.push(said.clone());
420 }
421 }
422 _ => {}
423 }
424 }
425 refs
426}
427
428#[cfg(test)]
429mod test {
430 use super::*;
431 use crate::data_storage::InMemoryDataStorage;
432 use crate::repositories::SQLiteConfig;
433
434 #[test]
435 fn facade_get_ocafile() -> Result<(), Vec<String>> {
436 let db = InMemoryDataStorage::new();
437 let db_cache = InMemoryDataStorage::new();
438 let cache_storage_config = SQLiteConfig::build().unwrap();
439 let mut facade = Facade::new(Box::new(db), Box::new(db_cache), cache_storage_config);
440 let ocafile_input = r#"
441ADD ATTRIBUTE d=Text i=Text passed=Boolean
442ADD META en PROPS description="Entrance credential" name="Entrance credential"
443ADD CHARACTER_ENCODING ATTRS d="utf-8" i="utf-8" passed="utf-8"
444ADD CONFORMANCE ATTRS d="M" i="M" passed="M"
445ADD LABEL en ATTRS d="Schema digest" i="Credential Issuee" passed="Passed"
446ADD INFORMATION en ATTRS d="Schema digest" i="Credential Issuee" passed="Enables or disables passing"
447ADD FORMAT ATTRS d="image/jpeg"
448ADD UNIT ATTRS i=m
449ADD ATTRIBUTE list=Array[Text] el=Text
450ADD CARDINALITY ATTRS list="1-2"
451ADD ENTRY_CODE ATTRS list="entry_code_said" el=["o1", "o2", "o3"]
452ADD ENTRY en ATTRS list="refs:ENrf7niTCnz7HD-Ci88rlxHlxkpQ2NIZNNv08fQnXANI" el={"o1": "o1_label", "o2": "o2_label", "o3": "o3_label"}
453"#.to_string();
454 let oca_bundle = facade.build_from_ocafile(ocafile_input);
455 let oca_bundle = oca_bundle.unwrap();
456 let ocafile = facade.parse_oca_bundle_to_ocafile(&oca_bundle)?;
457 let new_bundle = facade.build_from_ocafile(ocafile);
458 match new_bundle {
459 Ok(new_bundle) => {
460 assert_eq!(oca_bundle.said, new_bundle.said);
461 }
462 Err(e) => {
463 println!("{:#?}", e);
464 panic!("Faild to load oca bundle");
465 }
466 }
467
468 Ok(())
469 }
470}