Skip to main content

rocraters/ro_crate/
data_entity.rs

1//! Create a Data entity.
2//!
3//! A data entity is a computional research object such as a file that is
4//! required to fully understand or reproduce a research outcome.
5
6use crate::ro_crate::constraints::*;
7use crate::ro_crate::modify::*;
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{self, MapAccess, Visitor},
11};
12use std::collections::HashMap;
13use std::fmt;
14
15/// Represents a data entity with an identifier, type, and dynamic properties.
16///
17/// `DataEntity` is designed to encapsulate an entity with a unique identifier (`id`),
18/// a specific type (`type_`), and a set of dynamic properties (`dynamic_entity`).
19/// This struct is used to handle Data entities that have a predefined structure along with
20/// additional properties that may vary.
21#[derive(Debug, Clone)]
22pub struct DataEntity {
23    /// URI to data
24    pub id: String,
25    /// Defined type, if file MUST be type 'File'
26    pub type_: DataType,
27    /// Additional metadata
28    pub dynamic_entity: Option<HashMap<String, EntityValue>>,
29}
30
31/// Provides functionality for manipulating dynamic properties of a `DataEntity`.
32///
33/// This trait implementation allows for adding, modifying, and removing dynamic properties
34/// stored in the `dynamic_entity` field of `DataEntity`.
35impl DynamicEntityManipulation for DataEntity {
36    fn dynamic_entity(&mut self) -> &mut Option<HashMap<String, EntityValue>> {
37        &mut self.dynamic_entity
38    }
39
40    fn dynamic_entity_immut(&self) -> &Option<HashMap<String, EntityValue>> {
41        &self.dynamic_entity
42    }
43}
44
45impl DataEntity {
46    /// Gets ID and value of specific target property
47    pub fn get_property_value(&self, property: &str) -> Option<(String, EntityValue)> {
48        // Check the `type` field if it matches the property.
49        match property {
50            "@type" => Some((
51                self.id.clone(),
52                EntityValue::EntityDataType(self.type_.clone()),
53            )),
54            _ => self
55                .search_properties_for_value(property)
56                .map(|value| (self.id.clone(), value)),
57        }
58    }
59    /// Searches through every value in the struct to find the key for a matching input value.
60    ///
61    /// # Arguments
62    /// * `target_value` - The value to search for, as an `EntityValue`.
63    ///
64    /// # Returns
65    /// An `Option<String>` containing the key if the value exists, or `None` otherwise.
66    pub fn find_value_details(&self, target_value: &EntityValue) -> Option<(String, String)> {
67        // Check dynamic fields
68        if let Some(dynamic_entity) = &self.dynamic_entity {
69            if let Some(key) = search_dynamic_entity_for_key(dynamic_entity, target_value) {
70                return Some((self.id.clone(), key));
71            }
72        }
73
74        None
75    }
76    pub fn get_linked_ids(&self) -> Vec<Id> {
77        let mut ids = Vec::new();
78
79        // Check for Id in the dynamic entity
80        if let Some(dynamic_entity) = &self.dynamic_entity {
81            for value in dynamic_entity.values() {
82                Self::extract_ids_from_entity_value(value, &mut ids);
83            }
84        }
85
86        ids
87    }
88
89    /// Recursive helper to extract `Id` values from `EntityValue`.
90    fn extract_ids_from_entity_value(value: &EntityValue, ids: &mut Vec<Id>) {
91        match value {
92            EntityValue::EntityId(id) => {
93                ids.push(id.clone());
94            }
95            EntityValue::EntityVec(vec) => {
96                for v in vec {
97                    Self::extract_ids_from_entity_value(v, ids);
98                }
99            }
100            EntityValue::EntityObject(map) => {
101                for v in map.values() {
102                    Self::extract_ids_from_entity_value(v, ids);
103                }
104            }
105            EntityValue::EntityVecObject(vec_map) => {
106                for map in vec_map {
107                    for v in map.values() {
108                        Self::extract_ids_from_entity_value(v, ids);
109                    }
110                }
111            }
112            EntityValue::NestedDynamicEntity(nested_value) => {
113                Self::extract_ids_from_entity_value(nested_value, ids);
114            }
115            _ => {}
116        }
117    }
118}
119
120/// Custom display formatting for `DataEntity`
121impl fmt::Display for DataEntity {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(
124            f,
125            "ContextualEntity: id={}, type_={:?}, dynamic_entity={:?}",
126            self.id, self.type_, self.dynamic_entity
127        )
128    }
129}
130
131/// Provides custom serialization for `DataEntity`.
132///
133/// Implements custom serialization logic as defined in the `CustomSerialize` trait.
134/// This allows `DataEntity` to be serialized with custom rules, especially for the
135/// dynamic properties in the `dynamic_entity` field.
136impl CustomSerialize for DataEntity {
137    fn dynamic_entity(&self) -> Option<&HashMap<String, EntityValue>> {
138        self.dynamic_entity.as_ref()
139    }
140
141    fn id(&self) -> &String {
142        &self.id
143    }
144
145    fn type_(&self) -> &DataType {
146        &self.type_
147    }
148}
149
150/// Custom serde serialization implementation for `DataEntity`.
151///
152/// Delegates the serialization process to the `custom_serialize` method
153/// provided by the `CustomSerialize` trait implementation.
154impl Serialize for DataEntity {
155    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: Serializer,
158    {
159        self.custom_serialize(serializer)
160    }
161}
162
163/// Custom deserialization implementation for `DataEntity`.
164///
165/// This method provides a tailored approach to convert serialized data
166/// (like JSON) into a `DataEntity` instance. It employs a `DataEntityVisitor`
167/// for map-based deserialization.
168///
169/// The method expects the serialized data to be in a map format (key-value pairs),
170/// which is typical for formats like JSON. It specifically looks for `@id` and `@type`
171/// keys to fill the corresponding fields of `DataEntity`. All other keys are treated
172/// as dynamic properties and are stored in a `HashMap`.
173impl<'de> Deserialize<'de> for DataEntity {
174    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
175    where
176        D: Deserializer<'de>,
177    {
178        struct DataEntityVisitor;
179
180        impl<'de> Visitor<'de> for DataEntityVisitor {
181            type Value = DataEntity;
182
183            fn expecting(&self, formatter: &mut fmt::Formatter) -> std::fmt::Result {
184                formatter.write_str("a map representing a DataEntity")
185            }
186
187            fn visit_map<A>(self, mut map: A) -> Result<DataEntity, A::Error>
188            where
189                A: MapAccess<'de>,
190            {
191                let mut id = None;
192                let mut type_ = None;
193                let mut dynamic_entity: HashMap<String, EntityValue> = HashMap::new();
194
195                while let Some(key) = map.next_key::<String>()? {
196                    match key.as_str() {
197                        "@id" => id = Some(map.next_value()?),
198                        "@type" => type_ = Some(map.next_value()?),
199                        _ => {
200                            let value: EntityValue = map.next_value()?;
201                            dynamic_entity.insert(key, value);
202                        }
203                    }
204                }
205
206                let id = id.ok_or_else(|| de::Error::missing_field("@id"))?;
207                let type_ = type_.ok_or_else(|| de::Error::missing_field("@type"))?;
208
209                Ok(DataEntity {
210                    id,
211                    type_,
212                    dynamic_entity: Some(dynamic_entity),
213                })
214            }
215        }
216
217        deserializer.deserialize_map(DataEntityVisitor)
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_data_entity_creation() {
227        let entity = DataEntity {
228            id: "entity_id".to_string(),
229            type_: DataType::Term("ExampleType".to_string()),
230            dynamic_entity: Some(HashMap::new()),
231        };
232        assert_eq!(entity.id, "entity_id");
233        assert!(matches!(entity.type_, DataType::Term(ref t) if t == "ExampleType"));
234        assert!(entity.dynamic_entity.is_some());
235    }
236
237    #[test]
238    fn test_add_and_remove_dynamic_entity() {
239        let mut entity = DataEntity {
240            id: "entity_id".to_string(),
241            type_: DataType::Term("ExampleType".to_string()),
242            dynamic_entity: None,
243        };
244
245        // Adding a dynamic entity
246        entity.add_string_value("key".to_string(), "value".to_string());
247        assert_eq!(
248            entity.dynamic_entity().unwrap().get("key"),
249            Some(&EntityValue::EntityString("value".to_string()))
250        );
251
252        // Removing a dynamic entity
253        entity.remove_field("key");
254        assert!(entity.dynamic_entity().unwrap().get("key").is_none());
255    }
256
257    #[test]
258    fn test_serialization() {
259        let entity = DataEntity {
260            id: "entity_id".to_string(),
261            type_: DataType::Term("ExampleType".to_string()),
262            dynamic_entity: None,
263        };
264
265        let serialized = serde_json::to_string(&entity).unwrap();
266        assert!(serialized.contains("entity_id"));
267        assert!(serialized.contains("ExampleType"));
268    }
269
270    #[test]
271    fn test_deserialization() {
272        let json_data = r#"
273            {
274                "@id": "entity_id",
275                "@type": "ExampleType"
276            }
277        "#;
278        let deserialized: DataEntity = serde_json::from_str(json_data).unwrap();
279        assert_eq!(deserialized.id, "entity_id");
280        assert!(matches!(deserialized.type_, DataType::Term(ref t) if t == "ExampleType"));
281    }
282
283    #[test]
284    fn test_dynamic_entity_serialization() {
285        let mut entity = DataEntity {
286            id: "entity_id".to_string(),
287            type_: DataType::Term("ExampleType".to_string()),
288            dynamic_entity: None,
289        };
290        entity.add_string_value("key".to_string(), "value".to_string());
291
292        let serialized = serde_json::to_string(&entity).unwrap();
293        assert!(serialized.contains("\"key\":\"value\""));
294    }
295
296    #[test]
297    fn test_dynamic_entity_deserialization() {
298        let json_data = r#"
299            {
300                "@id": "entity_id",
301                "@type": "ExampleType",
302                "key": "value"
303            }
304        "#;
305        let deserialized: DataEntity = serde_json::from_str(json_data).unwrap();
306        assert_eq!(
307            deserialized.dynamic_entity.unwrap().get("key"),
308            Some(&EntityValue::EntityString("value".to_string()))
309        );
310    }
311
312    #[test]
313    fn test_deserialization_with_missing_fields() {
314        let json_data = r#"
315            {
316                "@id": "entity_id"
317            }
318        "#;
319        let result: Result<DataEntity, _> = serde_json::from_str(json_data);
320        assert!(result.is_err()); // Expecting an error due to missing @type field
321    }
322}