entrenar/research/ro_crate/
entity.rs1use serde::{Deserialize, Serialize};
4use serde_json::json;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub enum EntityType {
10 Dataset,
12 File,
14 Person,
16 Organization,
18 SoftwareApplication,
20 CreativeWork,
22 CreateAction,
24 Custom(String),
26}
27
28impl std::fmt::Display for EntityType {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 Self::Dataset => write!(f, "Dataset"),
32 Self::File => write!(f, "File"),
33 Self::Person => write!(f, "Person"),
34 Self::Organization => write!(f, "Organization"),
35 Self::SoftwareApplication => write!(f, "SoftwareApplication"),
36 Self::CreativeWork => write!(f, "CreativeWork"),
37 Self::CreateAction => write!(f, "CreateAction"),
38 Self::Custom(t) => write!(f, "{t}"),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct RoCrateEntity {
46 #[serde(rename = "@id")]
48 pub id: String,
49 #[serde(rename = "@type")]
51 pub type_field: String,
52 #[serde(flatten)]
54 pub properties: HashMap<String, serde_json::Value>,
55}
56
57impl RoCrateEntity {
58 pub fn new(id: impl Into<String>, entity_type: EntityType) -> Self {
60 Self { id: id.into(), type_field: entity_type.to_string(), properties: HashMap::new() }
61 }
62
63 pub fn with_property(
65 mut self,
66 key: impl Into<String>,
67 value: impl Into<serde_json::Value>,
68 ) -> Self {
69 self.properties.insert(key.into(), value.into());
70 self
71 }
72
73 pub fn with_name(self, name: impl Into<String>) -> Self {
75 self.with_property("name", name.into())
76 }
77
78 pub fn with_description(self, description: impl Into<String>) -> Self {
80 self.with_property("description", description.into())
81 }
82
83 pub fn with_reference(self, key: impl Into<String>, ref_id: impl Into<String>) -> Self {
85 self.with_property(key, json!({ "@id": ref_id.into() }))
86 }
87
88 pub fn with_references(
90 self,
91 key: impl Into<String>,
92 ref_ids: impl IntoIterator<Item = impl Into<String>>,
93 ) -> Self {
94 let refs: Vec<serde_json::Value> =
95 ref_ids.into_iter().map(|id| json!({ "@id": id.into() })).collect();
96 self.with_property(key, refs)
97 }
98
99 pub fn root_dataset() -> Self {
101 Self::new("./", EntityType::Dataset)
102 }
103
104 pub fn file(path: impl Into<String>) -> Self {
106 Self::new(path, EntityType::File)
107 }
108
109 pub fn person(id: impl Into<String>, name: impl Into<String>) -> Self {
111 Self::new(id, EntityType::Person).with_name(name)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_entity_type_display_dataset() {
121 assert_eq!(EntityType::Dataset.to_string(), "Dataset");
122 }
123
124 #[test]
125 fn test_entity_type_display_file() {
126 assert_eq!(EntityType::File.to_string(), "File");
127 }
128
129 #[test]
130 fn test_entity_type_display_person() {
131 assert_eq!(EntityType::Person.to_string(), "Person");
132 }
133
134 #[test]
135 fn test_entity_type_display_organization() {
136 assert_eq!(EntityType::Organization.to_string(), "Organization");
137 }
138
139 #[test]
140 fn test_entity_type_display_software_application() {
141 assert_eq!(EntityType::SoftwareApplication.to_string(), "SoftwareApplication");
142 }
143
144 #[test]
145 fn test_entity_type_display_creative_work() {
146 assert_eq!(EntityType::CreativeWork.to_string(), "CreativeWork");
147 }
148
149 #[test]
150 fn test_entity_type_display_create_action() {
151 assert_eq!(EntityType::CreateAction.to_string(), "CreateAction");
152 }
153
154 #[test]
155 fn test_entity_type_display_custom() {
156 let custom = EntityType::Custom("MyType".to_string());
157 assert_eq!(custom.to_string(), "MyType");
158 }
159
160 #[test]
161 fn test_entity_type_clone() {
162 let et = EntityType::Dataset;
163 let cloned = et.clone();
164 assert_eq!(et, cloned);
165 }
166
167 #[test]
168 fn test_entity_type_eq() {
169 assert_eq!(EntityType::File, EntityType::File);
170 assert_ne!(EntityType::File, EntityType::Person);
171 }
172
173 #[test]
174 fn test_ro_crate_entity_new() {
175 let entity = RoCrateEntity::new("test-id", EntityType::Dataset);
176 assert_eq!(entity.id, "test-id");
177 assert_eq!(entity.type_field, "Dataset");
178 assert!(entity.properties.is_empty());
179 }
180
181 #[test]
182 fn test_ro_crate_entity_with_property() {
183 let entity = RoCrateEntity::new("test", EntityType::File).with_property("size", 1024);
184 assert_eq!(entity.properties.get("size"), Some(&json!(1024)));
185 }
186
187 #[test]
188 fn test_ro_crate_entity_with_name() {
189 let entity = RoCrateEntity::new("test", EntityType::Dataset).with_name("My Dataset");
190 assert_eq!(entity.properties.get("name"), Some(&json!("My Dataset")));
191 }
192
193 #[test]
194 fn test_ro_crate_entity_with_description() {
195 let entity =
196 RoCrateEntity::new("test", EntityType::Dataset).with_description("A test dataset");
197 assert_eq!(entity.properties.get("description"), Some(&json!("A test dataset")));
198 }
199
200 #[test]
201 fn test_ro_crate_entity_with_reference() {
202 let entity =
203 RoCrateEntity::new("test", EntityType::File).with_reference("author", "#person1");
204 let expected = json!({ "@id": "#person1" });
205 assert_eq!(entity.properties.get("author"), Some(&expected));
206 }
207
208 #[test]
209 fn test_ro_crate_entity_with_references() {
210 let entity = RoCrateEntity::new("test", EntityType::Dataset)
211 .with_references("hasPart", vec!["file1.txt", "file2.txt"]);
212 let parts = entity.properties.get("hasPart").expect("key should exist");
213 assert!(parts.is_array());
214 let arr = parts.as_array().expect("operation should succeed");
215 assert_eq!(arr.len(), 2);
216 }
217
218 #[test]
219 fn test_ro_crate_entity_root_dataset() {
220 let entity = RoCrateEntity::root_dataset();
221 assert_eq!(entity.id, "./");
222 assert_eq!(entity.type_field, "Dataset");
223 }
224
225 #[test]
226 fn test_ro_crate_entity_file() {
227 let entity = RoCrateEntity::file("data/model.safetensors");
228 assert_eq!(entity.id, "data/model.safetensors");
229 assert_eq!(entity.type_field, "File");
230 }
231
232 #[test]
233 fn test_ro_crate_entity_person() {
234 let entity = RoCrateEntity::person("#alice", "Alice Smith");
235 assert_eq!(entity.id, "#alice");
236 assert_eq!(entity.type_field, "Person");
237 assert_eq!(entity.properties.get("name"), Some(&json!("Alice Smith")));
238 }
239
240 #[test]
241 fn test_ro_crate_entity_clone() {
242 let entity = RoCrateEntity::new("test", EntityType::File).with_name("test.txt");
243 let cloned = entity.clone();
244 assert_eq!(entity.id, cloned.id);
245 assert_eq!(entity.type_field, cloned.type_field);
246 }
247
248 #[test]
249 fn test_ro_crate_entity_serde() {
250 let entity = RoCrateEntity::new("test", EntityType::Dataset)
251 .with_name("Test Dataset")
252 .with_description("A test");
253
254 let json = serde_json::to_string(&entity).expect("JSON serialization should succeed");
255 let deserialized: RoCrateEntity =
256 serde_json::from_str(&json).expect("JSON deserialization should succeed");
257 assert_eq!(entity.id, deserialized.id);
258 assert_eq!(entity.type_field, deserialized.type_field);
259 }
260
261 #[test]
262 fn test_entity_type_serde() {
263 let et = EntityType::SoftwareApplication;
264 let json = serde_json::to_string(&et).expect("JSON serialization should succeed");
265 let deserialized: EntityType =
266 serde_json::from_str(&json).expect("JSON deserialization should succeed");
267 assert_eq!(et, deserialized);
268 }
269
270 #[test]
271 fn test_entity_type_debug() {
272 assert_eq!(format!("{:?}", EntityType::Dataset), "Dataset");
273 assert_eq!(format!("{:?}", EntityType::Custom("Foo".to_string())), "Custom(\"Foo\")");
274 }
275
276 #[test]
277 fn test_ro_crate_entity_chained_methods() {
278 let entity = RoCrateEntity::root_dataset()
279 .with_name("My Research Crate")
280 .with_description("Contains ML artifacts")
281 .with_reference("author", "#researcher")
282 .with_property("datePublished", "2024-01-15");
283
284 assert_eq!(entity.properties.len(), 4);
285 assert_eq!(entity.properties.get("name"), Some(&json!("My Research Crate")));
286 }
287}