1use super::Facade;
2use super::fetch::get_oca_bundle_model;
3use crate::data_storage::{DataStorage, Namespace};
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};
11#[cfg(feature = "local-references")]
12use log::debug;
13use log::info;
14use oca_ast::ast::{OCAAst, ObjectKind, RefValue, ReferenceAttrType};
15use oca_bundle::build::{OCABuild, OCABuildStep};
16use oca_bundle::state::oca_bundle::{OCABundle, OCABundleModel};
17use oca_dag::build_core_db_model;
18use oca_file::ocafile;
19use overlay_file::overlay_registry::OverlayLocalRegistry;
20use std::str::FromStr;
21#[cfg(feature = "local-references")]
22use said::SelfAddressingIdentifier;
23
24#[derive(thiserror::Error, Debug, serde::Serialize)]
25#[serde(untagged)]
26pub enum Error {
27 #[error("Validation error")]
28 ValidationError(Vec<ValidationError>),
29 #[error("Deprecated")]
30 Deprecated,
31}
32
33#[derive(thiserror::Error, Debug, serde::Serialize)]
34#[serde(untagged)]
35pub enum ValidationError {
36 #[error(transparent)]
37 OCAFileParse(#[from] oca_file::ocafile::error::ParseError),
38 #[error(transparent)]
39 OCABundleBuild(#[from] oca_bundle::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 #[error("Local references not enabled")]
53 LocalReferencesNotEnabled(),
54 #[error("Storage error: {0}")]
55 StorageError(String),
56}
57
58#[cfg(feature = "local-references")]
59impl References for Box<dyn DataStorage> {
60 fn find(&self, refn: &str) -> Option<String> {
61 self.get(Namespace::OCAReferences, refn)
62 .unwrap()
63 .map(|said| String::from_utf8(said).unwrap())
64 }
65
66 fn save(&mut self, refn: &str, value: String) {
67 self.insert(Namespace::OCAReferences, refn, value.to_string().as_bytes())
68 .unwrap()
69 }
70}
71
72#[cfg(feature = "local-references")]
73impl References for dyn DataStorage + '_ {
74 fn find(&self, refn: &str) -> Option<String> {
75 self.get(Namespace::OCAReferences, refn)
76 .unwrap()
77 .map(|said| String::from_utf8(said).unwrap())
78 }
79
80 fn save(&mut self, refn: &str, value: String) {
81 self.insert(Namespace::OCAReferences, refn, value.to_string().as_bytes())
82 .unwrap()
83 }
84}
85
86#[cfg(feature = "local-references")]
87struct DataStorageReferences<'a>(&'a mut dyn DataStorage);
88
89#[cfg(feature = "local-references")]
90impl References for DataStorageReferences<'_> {
91 fn find(&self, refn: &str) -> Option<String> {
92 self.0
93 .get(Namespace::OCAReferences, refn)
94 .unwrap()
95 .map(|said| String::from_utf8(said).unwrap())
96 }
97
98 fn save(&mut self, refn: &str, value: String) {
99 self.0
100 .insert(Namespace::OCAReferences, refn, value.to_string().as_bytes())
101 .unwrap()
102 }
103}
104
105pub fn build_from_ocafile(
107 ocafile: String,
108 registry: OverlayLocalRegistry,
109) -> Result<OCABundle, Error> {
110 let ast = ocafile::parse_from_string(ocafile.clone(), ®istry)
111 .map_err(|e| Error::ValidationError(vec![ValidationError::OCAFileParse(e)]))?;
112 let oca_build = oca_bundle::build::from_ast(None, &ast)
113 .map_err(|e| {
114 e.iter()
115 .map(|e| ValidationError::OCABundleBuild(e.clone()))
116 .collect::<Vec<_>>()
117 })
118 .map_err(Error::ValidationError)?;
119
120 let bundle = OCABundle::from(oca_build.oca_bundle.clone());
121 Ok(bundle)
122}
123
124pub fn parse_oca_bundle_to_ocafile(bundle: &OCABundleModel) -> String {
125 ocafile::generate_from_ast(&bundle.to_ast())
126}
127
128impl Facade {
129 #[cfg(not(feature = "local-references"))]
131 pub fn validate_ocafile(
132 &self,
133 ocafile: String,
134 registry: OverlayLocalRegistry,
135 ) -> Result<OCABuild, Vec<ValidationError>> {
136 let (base, oca_ast) = Self::parse_and_check_base(self.storage(), ocafile, registry, None)?;
137 oca_bundle::build::from_ast(base, &oca_ast).map_err(|e| {
138 e.iter()
139 .map(|e| ValidationError::OCABundleBuild(e.clone()))
140 .collect::<Vec<_>>()
141 })
142 }
143
144 #[cfg(feature = "local-references")]
149 pub fn validate_ocafile(
150 &mut self,
151 ocafile: String,
152 registry: OverlayLocalRegistry,
153 ) -> Result<OCABuild, Vec<ValidationError>> {
154 let storage: &dyn DataStorage = &*self.db_cache;
155 let (base, oca_ast) = {
156 let refs = DataStorageReferences(self.db.as_mut());
157 Self::parse_and_check_base(storage, ocafile, registry, Some(&refs))?
158 };
159 Self::oca_ast_to_oca_build_with_references(base, oca_ast, &mut self.db)
160 }
161
162 #[cfg(feature = "local-references")]
166 pub fn validate_ocafile_with_external_references<R: References>(
167 &self,
168 ocafile: String,
169 references: &mut R,
170 registry: OverlayLocalRegistry,
171 ) -> Result<OCABuild, Vec<ValidationError>> {
172 let refs: &dyn References = references;
173 let (base, oca_ast) =
174 Self::parse_and_check_base(self.storage(), ocafile, registry, Some(refs))?;
175 Self::oca_ast_to_oca_build_with_references(base, oca_ast, references)
176 }
177
178 pub fn build(&mut self, oca_build: &mut OCABuild) -> Result<OCABundleModel, Error> {
179 self.build_cache(&oca_build.oca_bundle);
180 self.build_meta(&oca_build.oca_bundle);
181
182 oca_build
183 .steps
184 .iter()
185 .for_each(|step| self.build_step(step));
186
187 let _ = self.add_relations(&oca_build.oca_bundle);
188
189 self.build_models(oca_build);
190
191 Ok(oca_build.oca_bundle.clone())
192 }
193
194 pub fn build_from_ocafile(
196 &mut self,
197 ocafile: String,
198 registry: OverlayLocalRegistry,
199 ) -> Result<OCABundleModel, Error> {
200 let mut oca_build = self
201 .validate_ocafile(ocafile, registry)
202 .map_err(Error::ValidationError)?;
203
204 self.build(&mut oca_build)
205 }
206
207 fn parse_and_check_base(
208 storage: &dyn DataStorage,
209 ocafile: String,
210 registry: OverlayLocalRegistry,
211 #[cfg(feature = "local-references")] references: Option<&dyn References>,
212 #[cfg(not(feature = "local-references"))] _references: Option<()>,
213 ) -> Result<(Option<OCABundleModel>, OCAAst), Vec<ValidationError>> {
214 let mut oca_ast = ocafile::parse_from_string(ocafile, ®istry).map_err(|e| {
216 vec![ValidationError::OCAFileParse(
217 oca_file::ocafile::error::ParseError::Custom(e.to_string()),
218 )]
219 })?;
220
221 let mut base: Option<OCABundleModel> = None;
224
225 if let Some(first_command) = oca_ast.commands.first()
226 && let (oca_ast::ast::CommandType::From, ObjectKind::OCABundle(content)) = (
227 first_command.clone().kind,
228 first_command.clone().object_kind,
229 )
230 {
231 let default_command_meta = oca_ast::ast::CommandMeta {
232 line_number: 0,
233 raw_line: "unknown".to_string(),
234 };
235 let command_meta = oca_ast
236 .commands_meta
237 .get(&0)
238 .unwrap_or(&default_command_meta);
239 let invalid_command = |message: String| {
240 Vec::from([ValidationError::InvalidCommand {
241 line_number: command_meta.line_number,
242 raw_line: command_meta.raw_line.clone(),
243 message,
244 }])
245 };
246
247 match content.said {
248 ReferenceAttrType::Reference(refs) => match refs {
249 RefValue::Said(said) => match get_oca_bundle_model(storage, said) {
250 Ok(bundle_model) => {
251 info!("Base OCABundle found: {:?}", bundle_model.digest);
252 base = Some(bundle_model.clone());
253 }
254 Err(e) => {
255 return Err(invalid_command(format!("Failed to load bundle: {:?}", e)));
256 }
257 },
258 RefValue::Name(name) => {
259 #[allow(unused_variables)]
260
261 #[cfg(feature = "local-references")]
262 {
263 match references.and_then(|refs| refs.find(&name)) {
264 Some(said) => match SelfAddressingIdentifier::from_str(&said) {
265 Ok(said_identifier) => {
266 match get_oca_bundle_model(storage, said_identifier) {
267 Ok(bundle_model) => {
268 info!(
269 "Base OCABundle found from refn: {:?}",
270 bundle_model.digest
271 );
272 base = Some(bundle_model.clone());
273 }
274 Err(e) => {
275 return Err(invalid_command(format!(
276 "Failed to load bundle: {:?}",
277 e
278 )));
279 }
280 }
281 }
282 Err(e) => {
283 return Err(invalid_command(format!(
284 "Invalid SAID format: {}",
285 e
286 )));
287 }
288 },
289 None => {
290 return Err(invalid_command(format!(
291 "Reference {} not found",
292 name
293 )));
294 }
295 }
296 }
297 #[cfg(not(feature = "local-references"))]
298 {
299 return Err(vec![ValidationError::LocalReferencesNotEnabled()]);
300 }
301 }
302 },
303 }
304 let _ = oca_ast.commands.remove(0);
306 }
307
308 Ok((base, oca_ast))
309 }
310
311 #[cfg(feature = "local-references")]
312 fn oca_ast_to_oca_build_with_references<R: References>(
313 base: Option<OCABundleModel>,
314 mut oca_ast: OCAAst,
315 references: &mut R,
316 ) -> Result<OCABuild, Vec<ValidationError>> {
317 local_references::replace_refn_with_refs(&mut oca_ast, references).map_err(|e| vec![e])?;
320
321 let oca_build = oca_bundle::build::from_ast(base, &oca_ast).map_err(|e| {
322 e.iter()
323 .map(|e| ValidationError::OCABundleBuild(e.clone()))
324 .collect::<Vec<_>>()
325 })?;
326
327 let schema_name = oca_ast.meta.get("name");
328 debug!("Schema name found: {:?}", schema_name);
329
330 if let Some(schema_name) = schema_name {
331 debug!("Said of new bundle: {:?}", oca_build.oca_bundle.digest);
332 let said = oca_build.oca_bundle.digest.clone().unwrap().to_string();
333 references.save(schema_name, said.clone());
334 };
335 Ok(oca_build)
336 }
337
338 fn build_cache(&self, oca_bundle: &OCABundleModel) {
339 let oca_bundle_cache_repo = OCABundleCacheRepo::new(self.connection());
340 let oca_bundle_cache_record = OCABundleCacheRecord::new(oca_bundle);
341 oca_bundle_cache_repo.insert(oca_bundle_cache_record);
342
343 let capture_base_cache_repo = CaptureBaseCacheRepo::new(self.connection());
344 let capture_base_cache_record = CaptureBaseCacheRecord::new(&oca_bundle.capture_base);
345 capture_base_cache_repo.insert(capture_base_cache_record);
346 }
347
348 fn build_step(&mut self, step: &OCABuildStep) {
349 let mut input: Vec<u8> = vec![];
350 match &step.parent_said {
351 Some(said) => {
352 input.push(said.to_string().len().try_into().unwrap());
353 input.extend(said.to_string().as_bytes());
354 }
355 None => {
356 input.push(0);
357 }
358 }
359
360 let command_str = serde_json::to_string(&step.command).unwrap();
361 input.extend(command_str.as_bytes());
362 let _ = self.db.insert(
363 Namespace::OCA,
364 &format!("oca.{}.operation", step.result.digest.clone().unwrap()),
365 &input,
366 );
367
368 let _ = self.db_cache.insert(
369 Namespace::OCABundlesJSON,
370 &step.result.digest.clone().unwrap().to_string(),
371 &serde_json::to_string(&step.result).unwrap().into_bytes(),
372 );
373 self.db_cache
374 .insert(
375 Namespace::OCAObjectsJSON,
376 &step.result.capture_base.digest.clone().unwrap().to_string(),
377 &serde_json::to_string(&step.result.capture_base)
378 .unwrap()
379 .into_bytes(),
380 )
381 .unwrap();
382 step.result.overlays.iter().for_each(|overlay| {
383 self.db_cache
384 .insert(
385 Namespace::OCAObjectsJSON,
386 &overlay.digest.clone().unwrap().to_string(),
387 &serde_json::to_string(&overlay).unwrap().into_bytes(),
388 )
389 .unwrap();
390 });
391 }
392
393 fn build_models(&mut self, oca_build: &OCABuild) {
394 let result_models = build_core_db_model(oca_build);
395 result_models.iter().for_each(|model| {
396 if let Some(command_model) = &model.command {
397 self.db
398 .insert(
399 Namespace::CoreModel,
400 &format!("core_model.{}", command_model.digest),
401 &command_model.json.clone().into_bytes(),
402 )
403 .unwrap();
404 }
405
406 if let Some(capture_base_model) = &model.capture_base {
407 let mut input: Vec<u8> = vec![];
408 match &capture_base_model.parent {
409 Some(said) => {
410 input.push(said.to_string().len().try_into().unwrap());
411 input.extend(said.to_string().as_bytes());
412 }
413 None => {
414 input.push(0);
415 }
416 }
417
418 input.push(
419 capture_base_model
420 .command_digest
421 .to_string()
422 .len()
423 .try_into()
424 .unwrap(),
425 );
426 input.extend(capture_base_model.command_digest.to_string().as_bytes());
427
428 self.db
429 .insert(
430 Namespace::CoreModel,
431 &format!("core_model.{}", capture_base_model.capture_base_said),
432 &input,
433 )
434 .unwrap();
435 }
436
437 if let Some(overlay_model) = &model.overlay {
438 let mut input: Vec<u8> = vec![];
439 match &overlay_model.parent {
440 Some(said) => {
441 input.push(said.to_string().len().try_into().unwrap());
442 input.extend(said.to_string().as_bytes());
443 }
444 None => {
445 input.push(0);
446 }
447 }
448
449 input.push(
450 overlay_model
451 .command_digest
452 .to_string()
453 .len()
454 .try_into()
455 .unwrap(),
456 );
457 input.extend(overlay_model.command_digest.to_string().as_bytes());
458
459 self.db
460 .insert(
461 Namespace::CoreModel,
462 &format!("core_model.{}", overlay_model.overlay_said),
463 &input,
464 )
465 .unwrap();
466 }
467
468 if let Some(oca_bundle_model) = &model.oca_bundle {
469 let mut input: Vec<u8> = vec![];
470 match &oca_bundle_model.parent {
471 Some(said) => {
472 input.push(said.to_string().len().try_into().unwrap());
473 input.extend(said.to_string().as_bytes());
474 }
475 None => {
476 input.push(0);
477 }
478 }
479
480 input.push(
481 oca_bundle_model
482 .capture_base_said
483 .to_string()
484 .len()
485 .try_into()
486 .unwrap(),
487 );
488 input.extend(oca_bundle_model.capture_base_said.to_string().as_bytes());
489
490 for said in &oca_bundle_model.overlays_said {
491 input.push(said.to_string().len().try_into().unwrap());
492 input.extend(said.to_string().as_bytes());
493 }
494
495 self.db
496 .insert(
497 Namespace::CoreModel,
498 &format!("core_model.{}", oca_bundle_model.oca_bundle_said),
499 &input,
500 )
501 .unwrap();
502 }
503 });
504 }
505}
506
507#[cfg(all(test, feature = "local-references"))]
508mod tests {
509 use super::*;
510 use crate::data_storage::{DataStorage, Namespace, SledDataStorage, SledDataStorageConfig};
511 use crate::repositories::SQLiteConfig;
512 use overlay_file::overlay_registry::OverlayLocalRegistry;
513 use std::fs;
514 use std::str::FromStr;
515 use std::time::{SystemTime, UNIX_EPOCH};
516
517 fn temp_storage_dir() -> std::path::PathBuf {
518 let mut path = std::env::temp_dir();
519 let nanos = SystemTime::now()
520 .duration_since(UNIX_EPOCH)
521 .unwrap()
522 .as_nanos();
523 path.push(format!("oca_store_refn_test_{}", nanos));
524 path
525 }
526
527 #[test]
528 fn stores_and_resolves_refn() {
529 let _ = env_logger::builder().is_test(true).try_init();
530 let storage_dir = temp_storage_dir();
531 fs::create_dir_all(&storage_dir).unwrap();
532
533 let storage_config = SledDataStorageConfig::build()
534 .path(storage_dir.clone())
535 .unwrap();
536 let db = SledDataStorage::new().config(storage_config);
537 let db_cache = db.clone();
538 let cache_storage_config = SQLiteConfig::build().unwrap();
539 let mut facade = Facade::new(Box::new(db), Box::new(db_cache), cache_storage_config);
540
541 let registry = OverlayLocalRegistry::from_dir("tests/core_overlays").unwrap();
542 let base_ocafile = r#"
543-- name=base
544ADD ATTRIBUTE name=Text
545"#
546 .to_string();
547 let _ = facade
548 .build_from_ocafile(base_ocafile, registry.clone())
549 .unwrap();
550
551 let stored = facade
552 .storage()
553 .get(Namespace::OCAReferences, "base")
554 .unwrap();
555 assert!(stored.is_some(), "refn should be stored in storage");
556
557 let refn_ocafile = r#"
558ADD ATTRIBUTE linked=refn:base
559"#
560 .to_string();
561 let bundle = facade.build_from_ocafile(refn_ocafile, registry).unwrap();
562
563 let stored = stored
564 .and_then(|bytes| String::from_utf8(bytes).ok())
565 .expect("refn should be stored as utf-8");
566 let stored_said = said::SelfAddressingIdentifier::from_str(&stored).unwrap();
567 match bundle.capture_base.attributes.get("linked") {
568 Some(oca_ast::ast::NestedAttrType::Reference(oca_ast::ast::RefValue::Said(said))) => {
569 assert_eq!(said, &stored_said);
570 }
571 other => panic!("expected linked to resolve to said, got {:?}", other),
572 }
573
574 let _ = fs::remove_dir_all(storage_dir);
575 }
576}