oca_rs/facade/
fetch.rs

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                // TODO
105                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(); // TODO
309            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    /// Retrive the ocafile for a given said
325    /// If dereference is true, all local references will be dereferenced to SAID
326    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    /// Retrive steps (AST representation) for a given said
347    ///
348    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
401/// Retrive all existing references from given OCA Bundle
402///
403/// # Arguments
404/// * `said` - SAID of the OCA Bundle
405///
406/// # Return
407/// * `Vec<String>` - Vector of all SAID references
408fn 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            // TODO(recursion) handle nested arrays
417            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}