oca_rs/facade/
build.rs

1use super::Facade;
2use crate::data_storage::{DataStorage, Namespace};
3use crate::facade::fetch::get_oca_bundle;
4#[cfg(feature = "local-references")]
5use crate::local_references;
6#[cfg(feature = "local-references")]
7pub use crate::local_references::References;
8use crate::repositories::{
9    CaptureBaseCacheRecord, CaptureBaseCacheRepo, OCABundleCacheRecord, OCABundleCacheRepo,
10    OCABundleFTSRecord, OCABundleFTSRepo,
11};
12#[cfg(feature = "local-references")]
13use log::debug;
14use oca_ast_semantics::ast::{OCAAst, ObjectKind, RefValue, ReferenceAttrType};
15use oca_bundle_semantics::build::{OCABuild, OCABuildStep};
16use oca_bundle_semantics::state::oca::OCABundle;
17use oca_bundle_semantics::Encode;
18use oca_dag_semantics::build_core_db_model;
19use said::derivation::HashFunctionCode;
20use said::sad::SerializationFormats;
21
22#[derive(thiserror::Error, Debug, serde::Serialize)]
23#[serde(untagged)]
24pub enum Error {
25    #[error("Validation error")]
26    ValidationError(Vec<ValidationError>),
27    #[error("Deprecated")]
28    Deprecated,
29}
30
31#[derive(thiserror::Error, Debug, serde::Serialize)]
32#[serde(untagged)]
33pub enum ValidationError {
34    #[error(transparent)]
35    OCAFileParse(#[from] oca_file::ocafile::error::ParseError),
36    #[error(transparent)]
37    OCABundleBuild(#[from] oca_bundle_semantics::build::Error),
38    #[error(transparent)]
39    TransformationBuild(#[from] transformation_file::build::Error),
40    #[error("Error at line {line_number} ({raw_line}): {message}")]
41    InvalidCommand {
42        #[serde(rename = "ln")]
43        line_number: usize,
44        #[serde(rename = "c")]
45        raw_line: String,
46        #[serde(rename = "e")]
47        message: String,
48    },
49    #[cfg(feature = "local-references")]
50    #[error("Reference {0} not found")]
51    UnknownRefn(String),
52}
53
54#[cfg(feature = "local-references")]
55impl References for Box<dyn DataStorage> {
56    fn find(&self, refn: &str) -> Option<String> {
57        self.get(Namespace::OCAReferences, refn)
58            .unwrap()
59            .map(|said| String::from_utf8(said).unwrap())
60    }
61
62    fn save(&mut self, refn: &str, value: String) {
63        self.insert(Namespace::OCAReferences, refn, value.to_string().as_bytes())
64            .unwrap()
65    }
66}
67
68pub fn build_from_ocafile(ocafile: String) -> Result<OCABundle, Error> {
69    let ast = oca_file::ocafile::parse_from_string(ocafile.clone())
70        .map_err(|e| Error::ValidationError(vec![ValidationError::OCAFileParse(e)]))?;
71    match ast {
72        oca_file::ocafile::OCAAst::TransformationAst(_) => Err(Error::Deprecated),
73        oca_file::ocafile::OCAAst::SemanticsAst(ast) => {
74            let oca_build = oca_bundle_semantics::build::from_ast(None, &ast)
75                .map_err(|e| {
76                    e.iter()
77                        .map(|e| ValidationError::OCABundleBuild(e.clone()))
78                        .collect::<Vec<_>>()
79                })
80                .map_err(Error::ValidationError)?;
81
82            Ok(oca_build.oca_bundle)
83        }
84    }
85}
86
87pub fn parse_oca_bundle_to_ocafile(bundle: &OCABundle) -> String {
88    oca_file_semantics::ocafile::generate_from_ast(&bundle.to_ast())
89}
90
91impl Facade {
92    #[cfg(not(feature = "local-references"))]
93    pub fn validate_ocafile(&self, ocafile: String) -> Result<OCABuild, Vec<ValidationError>> {
94        let (base, oca_ast) = Self::parse_and_check_base(self.storage(), ocafile)?;
95        oca_bundle_semantics::build::from_ast(base, &oca_ast).map_err(|e| {
96            e.iter()
97                .map(|e| ValidationError::OCABundleBuild(e.clone()))
98                .collect::<Vec<_>>()
99        })
100    }
101
102    /// Validate ocafile using external references for dereferencing `refn`.  It
103    /// won't update facade internal database with `refn`-> `said` mapping, so `refn`
104    /// can't be dereferenced in ocafiles processed later.
105    #[cfg(feature = "local-references")]
106    pub fn validate_ocafile_with_external_references<R: References>(
107        &self,
108        ocafile: String,
109        references: &mut R,
110    ) -> Result<OCABuild, Vec<ValidationError>> {
111        let (base, oca_ast) = Self::parse_and_check_base(self.storage(), ocafile)?;
112        Self::oca_ast_to_oca_build_with_references(base, oca_ast, references)
113    }
114
115    /// Validate ocafile using internal references for dereferencing `refn`.
116    /// Mapping between `refn` -> `said` is saved in facades database, so it can
117    /// be dereferenced in other ocafiles later.
118    #[cfg(feature = "local-references")]
119    pub fn validate_ocafile(&mut self, ocafile: String) -> Result<OCABuild, Vec<ValidationError>> {
120        let (base, oca_ast) = Self::parse_and_check_base(self.storage(), ocafile)?;
121        Self::oca_ast_to_oca_build_with_references(base, oca_ast, &mut self.db)
122    }
123
124    pub fn build(&mut self, oca_build: &OCABuild) -> Result<OCABundle, Error> {
125        self.build_cache(&oca_build.oca_bundle);
126
127        self.build_meta(&oca_build.oca_bundle);
128
129        oca_build
130            .steps
131            .iter()
132            .for_each(|step| self.build_step(step));
133
134        let _ = self.add_relations(oca_build.oca_bundle.clone());
135
136        self.build_models(oca_build);
137
138        Ok(oca_build.oca_bundle.clone())
139    }
140
141    pub fn build_from_ocafile(&mut self, ocafile: String) -> Result<OCABundle, Error> {
142        let ast = oca_file::ocafile::parse_from_string(ocafile.clone())
143            .map_err(|e| Error::ValidationError(vec![ValidationError::OCAFileParse(e)]))?;
144        match ast {
145            oca_file::ocafile::OCAAst::TransformationAst(_) => Err(Error::Deprecated),
146            oca_file::ocafile::OCAAst::SemanticsAst(_ast) => {
147                let oca_build = self
148                    .validate_ocafile(ocafile)
149                    .map_err(Error::ValidationError)?;
150
151                self.build(&oca_build)
152            }
153        }
154    }
155
156    fn parse_and_check_base(
157        storage: &dyn DataStorage,
158        ocafile: String,
159    ) -> Result<(Option<OCABundle>, OCAAst), Vec<ValidationError>> {
160        let mut errors: Vec<ValidationError> = vec![];
161        let mut oca_ast = oca_file_semantics::ocafile::parse_from_string(ocafile).map_err(|e| {
162            vec![ValidationError::OCAFileParse(
163                oca_file::ocafile::error::ParseError::SemanticsError(e),
164            )]
165        })?;
166
167        if !errors.is_empty() {
168            return Err(errors);
169        }
170
171        let mut base: Option<OCABundle> = None;
172        // TODO this should be avoided if the ast is passed for further processing, the base is
173        // checked again in generate bundle
174        if let Some(first_command) = oca_ast.commands.first() {
175            if let (oca_ast_semantics::ast::CommandType::From, ObjectKind::OCABundle(content)) = (
176                first_command.clone().kind,
177                first_command.clone().object_kind,
178            ) {
179                match content.said {
180                    ReferenceAttrType::Reference(refs) => {
181                        match refs {
182                            RefValue::Said(said) => {
183                                match get_oca_bundle(storage, said, false) {
184                                    Ok(oca_bundle) => {
185                                        // TODO
186                                        base = Some(oca_bundle.bundle.clone());
187                                    }
188                                    Err(e) => {
189                                        let default_command_meta =
190                                            oca_ast_semantics::ast::CommandMeta {
191                                                line_number: 0,
192                                                raw_line: "unknown".to_string(),
193                                            };
194                                        let command_meta = oca_ast
195                                            .commands_meta
196                                            .get(&0)
197                                            .unwrap_or(&default_command_meta);
198                                        e.iter().for_each(|e| {
199                                            errors.push(ValidationError::InvalidCommand {
200                                                line_number: command_meta.line_number,
201                                                raw_line: command_meta.raw_line.clone(),
202                                                message: e.clone(),
203                                            })
204                                        });
205                                    }
206                                }
207                            }
208                            RefValue::Name(_) => todo!(),
209                        }
210                    }
211                }
212                oca_ast.commands.remove(0);
213            }
214        };
215        Ok((base, oca_ast))
216    }
217
218    #[cfg(feature = "local-references")]
219    fn oca_ast_to_oca_build_with_references<R: References>(
220        base: Option<OCABundle>,
221        mut oca_ast: OCAAst,
222        references: &mut R,
223    ) -> Result<OCABuild, Vec<ValidationError>> {
224        // Dereference (refn -> refs) the AST before it start processing bundle steps, otherwise the SAID would
225        // not match.
226        local_references::replace_refn_with_refs(&mut oca_ast, references).map_err(|e| vec![e])?;
227
228        let oca_build = oca_bundle_semantics::build::from_ast(base, &oca_ast).map_err(|e| {
229            e.iter()
230                .map(|e| ValidationError::OCABundleBuild(e.clone()))
231                .collect::<Vec<_>>()
232        })?;
233
234        let schema_name = oca_ast.meta.get("name");
235        debug!("Schema name found: {:?}", schema_name);
236
237        if schema_name.is_some() {
238            let schema_name = schema_name.unwrap();
239            let said = oca_build.oca_bundle.said.clone().unwrap().to_string();
240            references.save(schema_name, said.clone());
241        };
242        Ok(oca_build)
243    }
244
245    fn build_cache(&self, oca_bundle: &OCABundle) {
246        let oca_bundle_cache_repo = OCABundleCacheRepo::new(self.connection());
247        let oca_bundle_cache_record = OCABundleCacheRecord::new(oca_bundle);
248        oca_bundle_cache_repo.insert(oca_bundle_cache_record);
249
250        let capture_base_cache_repo = CaptureBaseCacheRepo::new(self.connection());
251        let capture_base_cache_record = CaptureBaseCacheRecord::new(&oca_bundle.capture_base);
252        capture_base_cache_repo.insert(capture_base_cache_record);
253    }
254
255    fn build_meta(&self, oca_bundle: &OCABundle) {
256        let meta_overlays = oca_bundle
257            .overlays
258            .iter()
259            .filter_map(|x| {
260                x.as_any()
261                    .downcast_ref::<oca_bundle_semantics::state::oca::overlay::Meta>()
262            })
263            .collect::<Vec<_>>();
264        if !meta_overlays.is_empty() {
265            let oca_bundle_fts_repo = OCABundleFTSRepo::new(self.connection());
266            for meta_overlay in meta_overlays {
267                let oca_bundle_fts_record = OCABundleFTSRecord::new(
268                    oca_bundle.said.clone().unwrap().to_string(),
269                    meta_overlay
270                        .attr_pairs
271                        .get("name")
272                        .unwrap_or(&"".to_string())
273                        .clone(),
274                    meta_overlay
275                        .attr_pairs
276                        .get("description")
277                        .unwrap_or(&"".to_string())
278                        .clone(),
279                    meta_overlay.language,
280                );
281
282                oca_bundle_fts_repo.insert(oca_bundle_fts_record);
283            }
284        }
285    }
286
287    fn build_step(&mut self, step: &OCABuildStep) {
288        let mut input: Vec<u8> = vec![];
289        match &step.parent_said {
290            Some(said) => {
291                input.push(said.to_string().len().try_into().unwrap());
292                input.extend(said.to_string().as_bytes());
293            }
294            None => {
295                input.push(0);
296            }
297        }
298
299        let command_str = serde_json::to_string(&step.command).unwrap();
300        input.extend(command_str.as_bytes());
301        let result_bundle = step.result.clone();
302        self.db
303            .insert(
304                Namespace::OCA,
305                &format!("oca.{}.operation", result_bundle.said.clone().unwrap()),
306                &input,
307            )
308            .unwrap();
309
310        let code = HashFunctionCode::Blake3_256;
311        let format = SerializationFormats::JSON;
312        self.db_cache
313            .insert(
314                Namespace::OCABundlesJSON,
315                &result_bundle.said.clone().unwrap().to_string(),
316                &result_bundle.encode(&code, &format).unwrap(),
317            )
318            .unwrap();
319        self.db_cache
320            .insert(
321                Namespace::OCAObjectsJSON,
322                &result_bundle.capture_base.said.clone().unwrap().to_string(),
323                &serde_json::to_string(&result_bundle.capture_base)
324                    .unwrap()
325                    .into_bytes(),
326            )
327            .unwrap();
328        result_bundle.overlays.iter().for_each(|overlay| {
329            self.db_cache
330                .insert(
331                    Namespace::OCAObjectsJSON,
332                    &overlay.said().clone().unwrap().to_string(),
333                    &serde_json::to_string(&overlay).unwrap().into_bytes(),
334                )
335                .unwrap();
336        });
337    }
338
339    fn build_models(&mut self, oca_build: &OCABuild) {
340        let result_models = build_core_db_model(oca_build);
341        result_models.iter().for_each(|model| {
342            if let Some(command_model) = &model.command {
343                self.db
344                    .insert(
345                        Namespace::CoreModel,
346                        &format!("core_model.{}", command_model.digest),
347                        &command_model.json.clone().into_bytes(),
348                    )
349                    .unwrap();
350            }
351
352            if let Some(capture_base_model) = &model.capture_base {
353                let mut input: Vec<u8> = vec![];
354                match &capture_base_model.parent {
355                    Some(said) => {
356                        input.push(said.to_string().len().try_into().unwrap());
357                        input.extend(said.to_string().as_bytes());
358                    }
359                    None => {
360                        input.push(0);
361                    }
362                }
363
364                input.push(
365                    capture_base_model
366                        .command_digest
367                        .to_string()
368                        .len()
369                        .try_into()
370                        .unwrap(),
371                );
372                input.extend(capture_base_model.command_digest.to_string().as_bytes());
373
374                self.db
375                    .insert(
376                        Namespace::CoreModel,
377                        &format!("core_model.{}", capture_base_model.capture_base_said),
378                        &input,
379                    )
380                    .unwrap();
381            }
382
383            if let Some(overlay_model) = &model.overlay {
384                let mut input: Vec<u8> = vec![];
385                match &overlay_model.parent {
386                    Some(said) => {
387                        input.push(said.to_string().len().try_into().unwrap());
388                        input.extend(said.to_string().as_bytes());
389                    }
390                    None => {
391                        input.push(0);
392                    }
393                }
394
395                input.push(
396                    overlay_model
397                        .command_digest
398                        .to_string()
399                        .len()
400                        .try_into()
401                        .unwrap(),
402                );
403                input.extend(overlay_model.command_digest.to_string().as_bytes());
404
405                self.db
406                    .insert(
407                        Namespace::CoreModel,
408                        &format!("core_model.{}", overlay_model.overlay_said),
409                        &input,
410                    )
411                    .unwrap();
412            }
413
414            if let Some(oca_bundle_model) = &model.oca_bundle {
415                let mut input: Vec<u8> = vec![];
416                match &oca_bundle_model.parent {
417                    Some(said) => {
418                        input.push(said.to_string().len().try_into().unwrap());
419                        input.extend(said.to_string().as_bytes());
420                    }
421                    None => {
422                        input.push(0);
423                    }
424                }
425
426                input.push(
427                    oca_bundle_model
428                        .capture_base_said
429                        .to_string()
430                        .len()
431                        .try_into()
432                        .unwrap(),
433                );
434                input.extend(oca_bundle_model.capture_base_said.to_string().as_bytes());
435
436                for said in &oca_bundle_model.overlays_said {
437                    input.push(said.to_string().len().try_into().unwrap());
438                    input.extend(said.to_string().as_bytes());
439                }
440
441                self.db
442                    .insert(
443                        Namespace::CoreModel,
444                        &format!("core_model.{}", oca_bundle_model.oca_bundle_said),
445                        &input,
446                    )
447                    .unwrap();
448            }
449        });
450    }
451}