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 #[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 #[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 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 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 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}