From 032179614d9ecea2ea873554ea33c9911d575d37 Mon Sep 17 00:00:00 2001 From: Marcin Olichwiruk <21108638+olichwiruk@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:14:28 +0100 Subject: [PATCH] feat: add AttributeFraming overlay and related structures to enhance attribute management --- semantics/oca-ast/src/ast/mod.rs | 20 +++ semantics/oca-bundle/src/state/attribute.rs | 8 +- semantics/oca-bundle/src/state/oca/overlay.rs | 2 + .../state/oca/overlay/attribute_framing.rs | 159 ++++++++++++++++++ 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 semantics/oca-bundle/src/state/oca/overlay/attribute_framing.rs diff --git a/semantics/oca-ast/src/ast/mod.rs b/semantics/oca-ast/src/ast/mod.rs index cf22a9b..0e1ca63 100644 --- a/semantics/oca-ast/src/ast/mod.rs +++ b/semantics/oca-ast/src/ast/mod.rs @@ -156,6 +156,7 @@ pub enum OverlayType { Layout, Sensitivity, Link, + AttributeFraming } impl Serialize for OverlayType { @@ -188,6 +189,7 @@ impl Serialize for OverlayType { 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"), + OverlayType::AttributeFraming => serializer.serialize_str("spec/overlays/attribute_framing/1.0"), } } } @@ -243,6 +245,7 @@ impl FromStr for OverlayType { "Layout" => Ok(OverlayType::Layout), "Sensitivity" => Ok(OverlayType::Sensitivity), "Link" => Ok(OverlayType::Link), + "AttributeFraming" => Ok(OverlayType::AttributeFraming), _ => Err(()), } } @@ -271,6 +274,7 @@ impl fmt::Display for OverlayType { OverlayType::Layout => write!(f, "Layout"), OverlayType::Sensitivity => write!(f, "Sensitivity"), OverlayType::Link => write!(f, "Link"), + OverlayType::AttributeFraming => write!(f, "AttributeFraming"), } } } @@ -302,6 +306,7 @@ impl<'de> Deserialize<'de> for OverlayType { "spec/overlays/layout/1.0" => Ok(OverlayType::Layout), "spec/overlays/sensitivity/1.0" => Ok(OverlayType::Sensitivity), "spec/overlays/link/1.0" => Ok(OverlayType::Link), + "spec/overlays/attribute_framing/1.0" => Ok(OverlayType::AttributeFraming), _ => Err(serde::de::Error::custom(format!( "unknown overlay type: {}", s @@ -771,6 +776,13 @@ impl From for ObjectKind { properties: None, }, ), + 22 => ObjectKind::Overlay( + OverlayType::AttributeFraming, + Content { + attributes: None, + properties: None, + }, + ), _ => panic!("Unknown object type"), } } @@ -801,6 +813,7 @@ impl From for u8 { ObjectKind::Overlay(OverlayType::Layout, _) => 19, ObjectKind::Overlay(OverlayType::Sensitivity, _) => 20, ObjectKind::Overlay(OverlayType::Link, _) => 21, + ObjectKind::Overlay(OverlayType::AttributeFraming, _) => 22, } } } @@ -960,6 +973,13 @@ impl<'de> Deserialize<'de> for ObjectKind { properties: None, }, )), + "AttributeFraming" => Ok(ObjectKind::Overlay( + OverlayType::AttributeFraming, + Content { + attributes: None, + properties: None, + }, + )), _ => Err(serde::de::Error::custom(format!( "unknown object kind: {}", s diff --git a/semantics/oca-bundle/src/state/attribute.rs b/semantics/oca-bundle/src/state/attribute.rs index 7dfa197..63476c3 100644 --- a/semantics/oca-bundle/src/state/attribute.rs +++ b/semantics/oca-bundle/src/state/attribute.rs @@ -5,7 +5,7 @@ use oca_ast_semantics::ast::NestedAttrType; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::state::{encoding::Encoding, entries::EntriesElement, entry_codes::EntryCodes}; +use crate::state::{encoding::Encoding, entries::EntriesElement, entry_codes::EntryCodes, oca::overlay::attribute_framing::Framing}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Attribute { pub name: String, @@ -29,6 +29,7 @@ pub struct Attribute { pub conformance: Option, pub standards: Option>, pub links: Option>, + pub framings: Option>, } impl Default for Attribute { @@ -60,6 +61,7 @@ impl Attribute { conformance: None, standards: None, links: None, + framings: None, } } @@ -134,6 +136,10 @@ impl Attribute { if other.links.is_some() { self.links.clone_from(&other.links); } + + if other.framings.is_some() { + self.framings.clone_from(&other.framings); + } } } diff --git a/semantics/oca-bundle/src/state/oca/overlay.rs b/semantics/oca-bundle/src/state/oca/overlay.rs index 293a508..6db8d8e 100644 --- a/semantics/oca-bundle/src/state/oca/overlay.rs +++ b/semantics/oca-bundle/src/state/oca/overlay.rs @@ -15,6 +15,7 @@ pub mod standard; pub mod subset; pub mod unit; pub mod link; +pub mod attribute_framing; pub use self::attribute_mapping::AttributeMappingOverlay as AttributeMapping; pub use self::cardinality::CardinalityOverlay as Cardinality; @@ -32,6 +33,7 @@ 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 self::attribute_framing::AttributeFramingOverlay as AttributeFraming; pub use oca_ast_semantics::ast::OverlayType; use said::derivation::HashFunctionCode; diff --git a/semantics/oca-bundle/src/state/oca/overlay/attribute_framing.rs b/semantics/oca-bundle/src/state/oca/overlay/attribute_framing.rs new file mode 100644 index 0000000..93892a7 --- /dev/null +++ b/semantics/oca-bundle/src/state/oca/overlay/attribute_framing.rs @@ -0,0 +1,159 @@ +use crate::state::{attribute::Attribute, oca::Overlay}; +use isolang::Language; +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 type Framing = HashMap; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FramingScope { + pub predicate_id: String, + pub framing_justification: String, +} + +pub trait Framings { + fn set_framing(&mut self, id: String, framing: Framing); +} + +impl Framings for Attribute { + fn set_framing(&mut self, id: String, framing: Framing) { + match self.framings { + Some(ref mut framings) => { + if let Some(f) = framings.get_mut(&id) { + f.extend(framing); + } else { + framings.insert(id, framing); + } + } + None => { + let mut framings = HashMap::new(); + framings.insert(id, framing); + self.framings = Some(framings); + } + } + } +} + +pub fn serialize_framing( + 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 { + let sorted_framings: BTreeMap<_, _> = v.iter().collect(); + ser.serialize_entry(k, &sorted_framings)?; + } + ser.end() +} + +#[derive(SAD, Serialize, Deserialize, Debug, Clone)] +pub struct AttributeFramingOverlay { + #[said] + #[serde(rename = "d")] + said: Option, + capture_base: Option, + #[serde(rename = "type")] + overlay_type: OverlayType, + #[serde(rename = "framing_metadata")] + pub metadata: HashMap, + #[serde(serialize_with = "serialize_framing")] + pub attribute_framing: HashMap, +} + +impl Overlay for AttributeFramingOverlay { + 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 language(&self) -> Option<&Language> { + None + } + fn attributes(&self) -> Vec<&String> { + self.attribute_framing.keys().collect::>() + } + /// Add an attribute to the Label Overlay + /// TODO add assignment of attribute to category + fn add(&mut self, attribute: &Attribute) { + if let Some(id) = self.metadata.get("frame_id") { + if let Some(framing) = &attribute.framings { + if let Some(value) = framing.get(id) { + self.attribute_framing + .insert(attribute.name.clone(), value.clone()); + } + } + } + } +} + +impl AttributeFramingOverlay { + pub fn new(id: String) -> Self { + let mut metadata = HashMap::new(); + metadata.insert("frame_id".to_string(), id); + Self { + capture_base: None, + said: None, + overlay_type: OverlayType::AttributeFraming, + metadata, + attribute_framing: HashMap::new(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_attribute_framing_overlay() { + let mut overlay = AttributeFramingOverlay::new("frame_id".to_string()); + let mut loc1 = HashMap::new(); + loc1.insert( + "http://loc.1".to_string(), + FramingScope { + predicate_id: "skos:exactMatch".to_string(), + framing_justification: "semapv:ManualMappingCuration" + .to_string(), + }, + ); + let mut loc2 = HashMap::new(); + loc2.insert( + "http://loc.2".to_string(), + FramingScope { + predicate_id: "skos:exactMatch".to_string(), + framing_justification: "semapv:ManualMappingCuration" + .to_string(), + }, + ); + let attr = cascade! { + Attribute::new("attr1".to_string()); + ..set_framing("frame_id".to_string(), loc1); + ..set_framing("frame_id".to_string(), loc2); + }; + // even that attribute has 2 lagnuage only one attribute should be added to the overlay according to it's language + overlay.add(&attr); + + assert_eq!(overlay.overlay_type, OverlayType::AttributeFraming); + assert_eq!(overlay.attribute_framing.len(), 1); + } +}