From 5d662427e0c8f748e9516cd68e7619212659740a Mon Sep 17 00:00:00 2001 From: Marcin Olichwiruk <21108638+olichwiruk@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:36:51 +0100 Subject: [PATCH] feat: add Link overlay --- oca/src/local_references.rs | 29 +++++- semantics/oca-ast/src/ast/mod.rs | 20 ++++ semantics/oca-bundle/src/build.rs | 29 ++++++ semantics/oca-bundle/src/state/attribute.rs | 6 ++ semantics/oca-bundle/src/state/oca.rs | 30 ++++++ semantics/oca-bundle/src/state/oca/overlay.rs | 2 + .../oca-bundle/src/state/oca/overlay/link.rs | 94 +++++++++++++++++++ semantics/oca-file/src/ocafile.pest | 3 + .../oca-file/src/ocafile/instructions/add.rs | 6 ++ .../src/ocafile/instructions/helpers.rs | 16 ++++ tests/build_from_ocafile.rs | 30 ++++++ 11 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 semantics/oca-bundle/src/state/oca/overlay/link.rs diff --git a/oca/src/local_references.rs b/oca/src/local_references.rs index 8fa4ecd..061c3c5 100644 --- a/oca/src/local_references.rs +++ b/oca/src/local_references.rs @@ -1,6 +1,9 @@ use std::str::FromStr; -use oca_ast_semantics::ast::{CommandType, NestedAttrType, OCAAst, ObjectKind, RefValue}; +use oca_ast_semantics::ast::{ + CommandType, NestedAttrType, NestedValue, OCAAst, ObjectKind, OverlayType, + RefValue, +}; use said::SelfAddressingIdentifier; use crate::facade::build::ValidationError; @@ -48,6 +51,30 @@ pub fn replace_refn_with_refs( } } } + + if let ( + CommandType::Add, + ObjectKind::Overlay(OverlayType::Link, content), + ) = (&command.kind, &mut command.object_kind) + { + if let Some(properties) = &mut content.properties { + if let Some(NestedValue::Reference(RefValue::Name(refn))) = + properties.get("target") + { + if let Some(said) = references.find(refn) { + let said = + SelfAddressingIdentifier::from_str(&said).unwrap(); // todo + properties.insert( + "target".to_string(), + NestedValue::Reference(RefValue::Said(said)), + ); + *properties = properties.clone(); + } else { + return Err(ValidationError::UnknownRefn(refn.clone())); + } + } + } + } } Ok(()) } diff --git a/semantics/oca-ast/src/ast/mod.rs b/semantics/oca-ast/src/ast/mod.rs index d0b6878..cf22a9b 100644 --- a/semantics/oca-ast/src/ast/mod.rs +++ b/semantics/oca-ast/src/ast/mod.rs @@ -155,6 +155,7 @@ pub enum OverlayType { UnitMapping, Layout, Sensitivity, + Link, } impl Serialize for OverlayType { @@ -186,6 +187,7 @@ impl Serialize for OverlayType { OverlayType::UnitMapping => serializer.serialize_str("spec/overlays/unit_mapping/1.0"), OverlayType::Layout => serializer.serialize_str("spec/overlays/layout/1.0"), OverlayType::Sensitivity => serializer.serialize_str("spec/overlays/sensitivity/1.0"), + OverlayType::Link => serializer.serialize_str("spec/overlays/link/1.0"), } } } @@ -240,6 +242,7 @@ impl FromStr for OverlayType { "UnitMapping" => Ok(OverlayType::UnitMapping), "Layout" => Ok(OverlayType::Layout), "Sensitivity" => Ok(OverlayType::Sensitivity), + "Link" => Ok(OverlayType::Link), _ => Err(()), } } @@ -267,6 +270,7 @@ impl fmt::Display for OverlayType { OverlayType::UnitMapping => write!(f, "UnitMapping"), OverlayType::Layout => write!(f, "Layout"), OverlayType::Sensitivity => write!(f, "Sensitivity"), + OverlayType::Link => write!(f, "Link"), } } } @@ -297,6 +301,7 @@ impl<'de> Deserialize<'de> for OverlayType { "spec/overlays/unit_mapping/1.0" => Ok(OverlayType::UnitMapping), "spec/overlays/layout/1.0" => Ok(OverlayType::Layout), "spec/overlays/sensitivity/1.0" => Ok(OverlayType::Sensitivity), + "spec/overlays/link/1.0" => Ok(OverlayType::Link), _ => Err(serde::de::Error::custom(format!( "unknown overlay type: {}", s @@ -759,6 +764,13 @@ impl From for ObjectKind { properties: None, }, ), + 21 => ObjectKind::Overlay( + OverlayType::Link, + Content { + attributes: None, + properties: None, + }, + ), _ => panic!("Unknown object type"), } } @@ -788,6 +800,7 @@ impl From for u8 { ObjectKind::Overlay(OverlayType::UnitMapping, _) => 18, ObjectKind::Overlay(OverlayType::Layout, _) => 19, ObjectKind::Overlay(OverlayType::Sensitivity, _) => 20, + ObjectKind::Overlay(OverlayType::Link, _) => 21, } } } @@ -940,6 +953,13 @@ impl<'de> Deserialize<'de> for ObjectKind { properties: None, }, )), + "Link" => Ok(ObjectKind::Overlay( + OverlayType::Link, + Content { + attributes: None, + properties: None, + }, + )), _ => Err(serde::de::Error::custom(format!( "unknown object kind: {}", s diff --git a/semantics/oca-bundle/src/build.rs b/semantics/oca-bundle/src/build.rs index 8060015..76fe885 100644 --- a/semantics/oca-bundle/src/build.rs +++ b/semantics/oca-bundle/src/build.rs @@ -10,6 +10,7 @@ use crate::state::oca::overlay::information::Information; use crate::state::oca::overlay::label::Labels; use crate::state::oca::overlay::meta::Metas; use crate::state::oca::overlay::unit::Units; +use crate::state::oca::overlay::link::Links; use crate::state::oca::OCABundle; use crate::state::{ attribute::Attribute, encoding::Encoding, entries::EntriesElement, @@ -442,6 +443,34 @@ pub fn apply_command(base: Option, op: ast::Command) -> Result { + let mut target_bundle = None; + if let Some(ref properties) = content.properties { + if let Some(ast::NestedValue::Reference(ast::RefValue::Said(target_said))) = properties.get("target") { + target_bundle = Some(target_said.to_string()); + } + } + if target_bundle.is_none() { + errors.push("Undefined target bundle".to_string()); + } + + if let Some(ref attributes) = content.attributes { + for (attr_name, attr_type_value) in attributes { + let mut attribute = oca + .attributes + .get(attr_name) + .ok_or_else(|| { + errors.push(format!("Undefined attribute: {attr_name}")); + errors.clone() + })? + .clone(); + if let ast::NestedValue::Value(linked_attr) = attr_type_value { + attribute.set_link(target_bundle.clone().unwrap(), linked_attr.clone()); + } + oca.add_attribute(attribute); + } + } + } _ => (), } } diff --git a/semantics/oca-bundle/src/state/attribute.rs b/semantics/oca-bundle/src/state/attribute.rs index e15b80f..7dfa197 100644 --- a/semantics/oca-bundle/src/state/attribute.rs +++ b/semantics/oca-bundle/src/state/attribute.rs @@ -28,6 +28,7 @@ pub struct Attribute { pub cardinality: Option, pub conformance: Option, pub standards: Option>, + pub links: Option>, } impl Default for Attribute { @@ -58,6 +59,7 @@ impl Attribute { cardinality: None, conformance: None, standards: None, + links: None, } } @@ -128,6 +130,10 @@ impl Attribute { if other.standards.is_some() { self.standards.clone_from(&other.standards); } + + if other.links.is_some() { + self.links.clone_from(&other.links); + } } } diff --git a/semantics/oca-bundle/src/state/oca.rs b/semantics/oca-bundle/src/state/oca.rs index 5a331ec..d5451fd 100644 --- a/semantics/oca-bundle/src/state/oca.rs +++ b/semantics/oca-bundle/src/state/oca.rs @@ -274,6 +274,26 @@ impl OCABox { } } } + + if let Some(links) = &attribute.links { + for target_bundle in links.keys() { + let mut link_ov = overlays.iter_mut().find(|x| { + match x.as_any().downcast_ref::() { + Some(o) => o.target_bundle == *target_bundle, + None => false, + } + }); + + if link_ov.is_none() { + overlays + .push(Box::new(overlay::Link::new(target_bundle.clone()))); + link_ov = overlays.last_mut(); + } + if let Some(ov) = link_ov { + ov.add(attribute); + } + } + } } overlays @@ -474,6 +494,15 @@ impl<'de> Deserialize<'de> for DynOverlay { })?, )); } + OverlayType::Link => { + return Ok(Box::new( + de_overlay + .deserialize_into::() + .map_err(|e| { + serde::de::Error::custom(format!("Link overlay: {e}")) + })?, + )); + } _ => { return Err(serde::de::Error::custom(format!( "Overlay type not supported: {:?}", @@ -511,6 +540,7 @@ where OverlayType::Entry, OverlayType::Label, OverlayType::Information, + OverlayType::Link, ]; let mut overlays_map: BTreeMap = BTreeMap::new(); diff --git a/semantics/oca-bundle/src/state/oca/overlay.rs b/semantics/oca-bundle/src/state/oca/overlay.rs index ec3319d..293a508 100644 --- a/semantics/oca-bundle/src/state/oca/overlay.rs +++ b/semantics/oca-bundle/src/state/oca/overlay.rs @@ -14,6 +14,7 @@ pub mod meta; pub mod standard; pub mod subset; pub mod unit; +pub mod link; pub use self::attribute_mapping::AttributeMappingOverlay as AttributeMapping; pub use self::cardinality::CardinalityOverlay as Cardinality; @@ -30,6 +31,7 @@ pub use self::label::LabelOverlay as Label; pub use self::meta::MetaOverlay as Meta; pub use self::standard::StandardOverlay as Standard; pub use self::subset::SubsetOverlay as Subset; +pub use self::link::LinkOverlay as Link; pub use oca_ast_semantics::ast::OverlayType; use said::derivation::HashFunctionCode; diff --git a/semantics/oca-bundle/src/state/oca/overlay/link.rs b/semantics/oca-bundle/src/state/oca/overlay/link.rs new file mode 100644 index 0000000..5ddcd65 --- /dev/null +++ b/semantics/oca-bundle/src/state/oca/overlay/link.rs @@ -0,0 +1,94 @@ +use crate::state::{attribute::Attribute, oca::Overlay}; +use oca_ast_semantics::ast::OverlayType; +use said::derivation::HashFunctionCode; +use said::{sad::SerializationFormats, sad::SAD}; +use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; +use std::any::Any; +use std::collections::HashMap; + +pub trait Links { + fn set_link(&mut self, t: String, link: String); +} + +impl Links for Attribute { + fn set_link(&mut self, t: String, link: String) { + if let Some(links) = &mut self.links { + links.insert(t, link); + } else { + let mut links = HashMap::new(); + links.insert(t, link); + self.links = Some(links); + } + } +} + +pub fn serialize_attributes( + attributes: &HashMap, + s: S, +) -> Result +where + S: Serializer, +{ + use std::collections::BTreeMap; + + let mut ser = s.serialize_map(Some(attributes.len()))?; + let sorted_attributes: BTreeMap<_, _> = attributes.iter().collect(); + for (k, v) in sorted_attributes { + ser.serialize_entry(k, v)?; + } + ser.end() +} + +#[derive(SAD, Serialize, Deserialize, Debug, Clone)] +pub struct LinkOverlay { + #[said] + #[serde(rename = "d")] + said: Option, + capture_base: Option, + #[serde(rename = "type")] + overlay_type: OverlayType, + pub target_bundle: String, + #[serde(serialize_with = "serialize_attributes")] + pub attribute_mapping: HashMap, +} + +impl Overlay for LinkOverlay { + fn as_any(&self) -> &dyn Any { + self + } + fn capture_base(&self) -> &Option { + &self.capture_base + } + fn set_capture_base(&mut self, said: &said::SelfAddressingIdentifier) { + self.capture_base = Some(said.clone()); + } + fn overlay_type(&self) -> &OverlayType { + &self.overlay_type + } + fn said(&self) -> &Option { + &self.said + } + fn attributes(&self) -> Vec<&String> { + self.attribute_mapping.keys().collect::>() + } + + fn add(&mut self, attribute: &Attribute) { + if let Some(links) = &attribute.links { + if let Some(value) = links.get(&self.target_bundle) { + self.attribute_mapping + .insert(attribute.name.clone(), value.to_string()); + } + } + } +} +impl LinkOverlay { + pub fn new(t: String) -> Self { + Self { + capture_base: None, + said: None, + overlay_type: OverlayType::Link, + target_bundle: t, + attribute_mapping: HashMap::new(), + } + } +} diff --git a/semantics/oca-file/src/ocafile.pest b/semantics/oca-file/src/ocafile.pest index 86c2387..f942313 100644 --- a/semantics/oca-file/src/ocafile.pest +++ b/semantics/oca-file/src/ocafile.pest @@ -131,6 +131,7 @@ oca_object = _{ cardinality | entry_code | entry | + link | flagged_attrs ) } @@ -151,6 +152,7 @@ remove_oca_object = _{ cardinality | entry_code | entry | + link | flagged_attrs ) } @@ -169,6 +171,7 @@ cardinality = {^"cardinality" ~ arg_ws ~ attrs_key ~ attr_key_pairs} entry_code = {^"entry_code" ~ arg_ws ~ attrs_key ~ attr_entry_code_key_pairs} entry = {^"entry" ~ arg_ws ~ lang ~ arg_ws ~ attrs_key ~ attr_entry_key_pairs} unit = {^"unit" ~ arg_ws ~ attrs_key ~ attr_key_pairs} +link = {^"link" ~ arg_ws ~ reference_type ~ arg_ws ~ attrs_key ~ attr_key_pairs} flagged_attrs = {^"flagged_attributes" ~ arg_ws ~ list_value} classification = { ^"classification" ~ arg_ws ~ classification_value} diff --git a/semantics/oca-file/src/ocafile/instructions/add.rs b/semantics/oca-file/src/ocafile/instructions/add.rs index 9030cbe..4dcdc3a 100644 --- a/semantics/oca-file/src/ocafile/instructions/add.rs +++ b/semantics/oca-file/src/ocafile/instructions/add.rs @@ -131,6 +131,12 @@ impl AddInstruction { helpers::extract_content(object), )); } + Rule::link => { + object_kind = Some(ObjectKind::Overlay( + OverlayType::Link, + helpers::extract_content(object), + )); + } Rule::flagged_attrs => { object_kind = Some(ObjectKind::CaptureBase(CaptureContent { properties: None, diff --git a/semantics/oca-file/src/ocafile/instructions/helpers.rs b/semantics/oca-file/src/ocafile/instructions/helpers.rs index b64da11..f9dd4c6 100644 --- a/semantics/oca-file/src/ocafile/instructions/helpers.rs +++ b/semantics/oca-file/src/ocafile/instructions/helpers.rs @@ -348,6 +348,22 @@ pub fn extract_properites_key_pairs(object: Pair) -> Option { + debug!("Parsing target alias: {:?}", attr.as_str()); + properties.insert( + "target".to_string(), + NestedValue::Reference(RefValue::Name(attr.as_str().to_string())) + ); + } + Rule::said => { + debug!("Parsing target said: {:?}", attr.as_str()); + if let Ok(said) = SelfAddressingIdentifier::from_str(attr.as_str()) { + properties.insert( + "target".to_string(), + NestedValue::Reference(RefValue::Said(said)), + ); + } + }, Rule::unit_system => { debug!("Parsing unit system: {:?}", attr.as_str()); properties.insert( diff --git a/tests/build_from_ocafile.rs b/tests/build_from_ocafile.rs index 7c2b19a..c7e92fd 100644 --- a/tests/build_from_ocafile.rs +++ b/tests/build_from_ocafile.rs @@ -129,6 +129,36 @@ ADD ATTRIBUTE x=Text Ok(()) } + #[test] + fn build_with_link() -> Result<(), Vec> { + let db = InMemoryDataStorage::new(); + let db_cache = InMemoryDataStorage::new(); + let cache_storage_config = SQLiteConfig::build().unwrap(); + let mut facade = Facade::new(Box::new(db), Box::new(db_cache), cache_storage_config); + let first_ocafile = r#" +-- name=first +ADD ATTRIBUTE a=Text +"# + .to_string(); + facade.build_from_ocafile(first_ocafile)?; + + let second_ocafile = r#" +-- name=second +ADD ATTRIBUTE b=Text +ADD LINK refn:first ATTRS b=a +"# + .to_string(); + + let result = facade.build_from_ocafile(second_ocafile)?; + + assert_eq!( + result.said.unwrap().to_string(), + "EPRLZDmCOMfUw4KE-BnRctyLmFhYrpSL0BlONgoKhgcR" + ); + + Ok(()) + } + #[test] fn fail_while_building_from_unknown_reference() { let db = InMemoryDataStorage::new();