oca_sdk_rs/
lib.rs

1//! # OCA SDK
2//!
3//! Compact yet powerful SDK for working with OCA bundles.
4//!
5//! # Features
6//!
7//! - Create OCA Bundle from OCAFile.
8//! - Validate OCA Bundle semantics.
9//! - Validate data against OCA Bundle.
10//! - Traverse through OCA Bundle attributes.
11pub mod data_validator;
12pub use oca_ast_semantics::ast::{
13    recursive_attributes::NestedAttrTypeFrame, AttributeType, NestedAttrType,
14    OverlayType, RefValue,
15};
16
17/// Performs semantic validation of an `OCABundle` and returns a status
18/// indicating whether the validation succeeded or failed, along with any associated errors.
19///
20/// Semantics validation ensures Bundle integrity, that is, that the Bundle identifier under `d`
21/// attribute matches the hash of the Bundle content.
22///
23/// # Arguments
24/// * `oca_bundle` - A reference to an `OCABundle` instance to be validated.
25///   The `OCABundle` contains the schema and data to be checked for semantic correctness.
26///
27/// # Returns
28/// * `Ok(SemanticValidationStatus::Valid)` - If the `OCABundle` passes all semantic validation checks.
29/// * `Ok(SemanticValidationStatus::Invalid(errors))` - If validation errors are found, with a vector of error messages.
30/// * `Err(String)` - If a critical error occurs during validation.
31///
32/// # Errors
33/// * Returns `Err` with a string message if the validation process encounters unexpected errors.
34///
35/// # Examples
36/// ```
37/// use std::fs;
38/// use std::path::Path;
39/// use oca_sdk_rs::{load, validate_semantics, SemanticValidationStatus};
40///
41/// let structural_bundle_path = Path::new("tests/assets/semantics/structural_bundle.json");
42/// let structural_bundle_str = fs::read_to_string(structural_bundle_path).expect("Failed to read the file");
43///
44/// let structural_bundle = load(&mut structural_bundle_str.as_bytes()).unwrap();
45///
46/// let semantics_validation_status = validate_semantics(&structural_bundle).unwrap();
47///
48/// match semantics_validation_status {
49///     SemanticValidationStatus::Valid => println!("The structural bundle is valid!"),
50///     SemanticValidationStatus::Invalid(errors) => {
51///         println!("Validation errors:");
52///         for error in errors {
53///             println!("  - {}", error);
54///         }
55///     }
56/// }
57/// ```
58pub use oca_bundle_semantics::state::validator::validate as validate_semantics;
59pub use oca_bundle_semantics::{
60    controller::load_oca as load,
61    state::{
62        attribute::Attribute,
63        oca::{overlay, OCABox, OCABundle},
64        validator::{SemanticValidationStatus, Validator as OCAValidator},
65    },
66};
67pub use oca_rs::facade::{
68    build::{build_from_ocafile, parse_oca_bundle_to_ocafile},
69    Facade,
70};
71use oca_rs::{EncodeBundle, HashFunctionCode, SerializationFormats};
72use std::collections::HashMap;
73use std::sync::{Arc, Mutex, Weak};
74
75pub trait ToJSON {
76    fn get_json_bundle(&self) -> String;
77}
78
79impl ToJSON for OCABundle {
80    fn get_json_bundle(&self) -> String {
81        let code = HashFunctionCode::Blake3_256;
82        let format = SerializationFormats::JSON;
83
84        String::from_utf8(self.encode(&code, &format).unwrap()).unwrap()
85    }
86}
87
88lazy_static::lazy_static! {
89    static ref INFO_CACHE: Mutex<HashMap<usize, Weak<OCABundleInfo>>> = Mutex::new(HashMap::new());
90}
91
92pub trait WithInfo {
93    fn info(&self) -> Arc<OCABundleInfo>;
94}
95
96impl WithInfo for OCABundle {
97    fn info(&self) -> Arc<OCABundleInfo> {
98        let key = self as *const OCABundle as usize;
99        let mut cache = INFO_CACHE.lock().unwrap();
100        if let Some(weak_info) = cache.get(&key) {
101            if let Some(info) = weak_info.upgrade() {
102                return info;
103            }
104        }
105
106        let new_info = Arc::new(OCABundleInfo::new(self));
107        cache.insert(key, Arc::downgrade(&new_info));
108        new_info
109    }
110}
111
112pub struct OCABundleInfo {
113    attributes: HashMap<String, Attribute>,
114    pub meta: HashMap<String, HashMap<String, String>>,
115    pub links: Vec<overlay::Link>,
116    pub framings: Vec<overlay::AttributeFraming>,
117}
118
119impl OCABundleInfo {
120    pub fn new(bundle: &OCABundle) -> Self {
121        let mut meta = HashMap::new();
122        let oca_box = OCABox::from(bundle.clone());
123        if let Some(m) = oca_box.meta {
124            m.iter().for_each(|(k, v)| {
125                meta.insert(k.to_639_3().to_string(), v.to_owned());
126            })
127        }
128
129        let mut overlays = bundle.overlays.clone();
130        let links: Vec<overlay::Link> = overlays
131            .iter_mut()
132            .filter(|o| o.as_any().downcast_ref::<overlay::Link>().is_some())
133            .map(|o| {
134                o.as_any()
135                    .downcast_ref::<overlay::Link>()
136                    .unwrap()
137                    .to_owned()
138            })
139            .collect();
140        let framings: Vec<overlay::AttributeFraming> = overlays
141            .iter_mut()
142            .filter(|o| {
143                o.as_any()
144                    .downcast_ref::<overlay::AttributeFraming>()
145                    .is_some()
146            })
147            .map(|o| {
148                o.as_any()
149                    .downcast_ref::<overlay::AttributeFraming>()
150                    .unwrap()
151                    .to_owned()
152            })
153            .collect();
154
155        Self {
156            attributes: oca_box.attributes,
157            meta,
158            links,
159            framings,
160        }
161    }
162
163    pub fn attributes(&self) -> impl Iterator<Item = &Attribute> {
164        self.attributes.values()
165    }
166
167    pub fn attribute(&self, name: &str) -> Option<&Attribute> {
168        self.attributes.get(name)
169    }
170}