rocraters/ro_crate/
context.rs1use log::debug;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4use std::collections::HashMap;
5use uuid::Uuid;
6
7use crate::ro_crate::schema::{self, RoCrateSchemaVersion};
8#[derive(Serialize, Deserialize, Debug, Clone)]
13#[serde(untagged)]
14pub enum RoCrateContext {
15 ReferenceContext(String),
18 ExtendedContext(Vec<ContextItem>),
20 EmbeddedContext(Vec<HashMap<String, String>>),
22}
23
24#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
33#[serde(untagged)]
34pub enum ContextItem {
35 ReferenceItem(String),
37 EmbeddedContext(HashMap<String, String>),
40}
41
42#[derive(Error, Debug)]
43pub enum ContextError {
44 #[error("{0}")]
45 NotFound(String),
46}
47
48impl RoCrateContext {
49 pub fn add_context(&mut self, new_context: &ContextItem) {
51 match self {
52 RoCrateContext::ReferenceContext(current) => {
53 *self = RoCrateContext::ExtendedContext(vec![
55 ContextItem::ReferenceItem(current.clone()),
56 new_context.clone(),
57 ]);
58 }
59 RoCrateContext::ExtendedContext(contexts) => {
60 contexts.push(new_context.clone());
62 }
63 RoCrateContext::EmbeddedContext(embedded_contexts) => {
64 if let ContextItem::EmbeddedContext(new_map) = new_context {
66 embedded_contexts.push(new_map.clone());
67 }
68 }
69 }
70 }
71
72 pub fn remove_context(&mut self, target: &ContextItem) -> Result<(), String> {
74 match self {
75 RoCrateContext::ReferenceContext(_) => Err(
76 "ReferenceContext cannot be removed as the crate requires a context.".to_string(),
77 ),
78 RoCrateContext::ExtendedContext(contexts) => {
79 let initial_len = contexts.len();
80 contexts.retain(|item| item != target);
81 if contexts.len() < initial_len {
82 Ok(())
83 } else {
84 Err("Context item not found in ExtendedContext.".to_string())
85 }
86 }
87 RoCrateContext::EmbeddedContext(embedded_contexts) => {
88 if let ContextItem::EmbeddedContext(target_map) = target {
89 let initial_len = embedded_contexts.len();
90 embedded_contexts.retain(|map| map != target_map);
91 if embedded_contexts.len() < initial_len {
92 Ok(())
93 } else {
94 Err("Target map not found in EmbeddedContext.".to_string())
95 }
96 } else {
97 Err("Invalid target type for EmbeddedContext.".to_string())
98 }
99 }
100 }
101 }
102
103 pub fn replace_context(
104 &mut self,
105 new_context: &ContextItem,
106 old_context: &ContextItem,
107 ) -> Result<(), ContextError> {
108 match self.remove_context(old_context) {
109 Ok(_removed) => {
110 self.add_context(new_context);
111 Ok(())
112 }
113 Err(e) => Err(ContextError::NotFound(e.to_string())),
114 }
115 }
116
117 pub fn get_all_context(&self) -> Vec<String> {
118 let mut valid_context: Vec<String> = Vec::new();
119 debug!("Self: {:?}", self);
120 match &self {
121 RoCrateContext::EmbeddedContext(context) => {
122 debug!("Found Embedded Context");
123 for map in context {
124 for key in map.keys() {
125 valid_context.push(key.to_string());
126 }
127 }
128 }
129 RoCrateContext::ExtendedContext(context) => {
130 debug!("Found Extended Context");
131 for map in context {
132 debug!("This is current map: {:?}", map);
133 match map {
134 ContextItem::EmbeddedContext(context) => {
135 debug!("Inside Embedded Context: {:?}", context);
136 for value in context.values() {
137 valid_context.push(value.to_string());
138 }
139 }
140 ContextItem::ReferenceItem(context) => {
141 debug!("Inside Reference Item: {:?}", context);
142 valid_context.push(context.to_string());
143 }
144 }
145 }
146 }
147 RoCrateContext::ReferenceContext(context) => {
148 debug!("Found Reference Context");
149 valid_context.push(context.to_string());
150 }
151 }
152 valid_context
153 }
154
155 pub fn get_specific_context(&self, context_key: &str) -> Option<String> {
156 match self {
157 RoCrateContext::ExtendedContext(context) => {
158 for item in context {
159 match item {
160 ContextItem::EmbeddedContext(embedded) => {
161 if let Some(value) = embedded.get(context_key) {
162 return Some(value.clone());
163 }
164 }
165 ContextItem::ReferenceItem(_) => {
166 }
168 }
169 }
170 None
171 }
172 RoCrateContext::ReferenceContext(_reference) => None,
173 RoCrateContext::EmbeddedContext(_context) => None,
174 }
175 }
176
177 pub fn add_urn_uuid(&mut self) {
178 match self {
179 RoCrateContext::ExtendedContext(context) => {
180 for x in context {
181 match x {
182 ContextItem::EmbeddedContext(embedded) => {
183 let urn_found = embedded
184 .get("@base")
185 .is_some_and(|value| value.starts_with("urn:uuid:"));
186 if !urn_found {
187 embedded.insert(
188 "@base".to_string(),
189 format!("urn:uuid:{}", Uuid::now_v7()),
190 );
191 }
192 }
193 ContextItem::ReferenceItem(_) => {
194 continue;
195 }
196 }
197 }
198 }
199 RoCrateContext::ReferenceContext(reference) => {
200 let mut base_map = HashMap::new();
201 base_map.insert("@base".to_string(), format!("urn:uuid:{}", Uuid::now_v7()));
202
203 *self = RoCrateContext::ExtendedContext(vec![
204 ContextItem::ReferenceItem(reference.clone()),
205 ContextItem::EmbeddedContext(base_map),
206 ]);
207 }
208 RoCrateContext::EmbeddedContext(context) => {
209 debug!("EmbeddedContext legacy of {:?}", context)
210 }
211 }
212 }
213
214 pub fn get_urn_uuid(&self) -> Option<String> {
215 let base = self.get_specific_context("@base");
216 base.map(|uuid| uuid.strip_prefix("urn:uuid:").unwrap().to_string())
217 }
218
219 pub fn get_schema_version(&self) -> Option<schema::RoCrateSchemaVersion> {
221 self.get_all_context().iter().find_map(|context_url| {
222 if context_url.contains("ro/crate/1.1") {
223 Some(RoCrateSchemaVersion::V1_1)
224 } else if context_url.contains("ro/crate/1.2") {
225 Some(RoCrateSchemaVersion::V1_2)
226 } else {
227 None
228 }
229 })
230 }
231}
232
233#[cfg(test)]
234mod write_crate_tests {
235 use crate::ro_crate::read::read_crate;
236 use crate::ro_crate::schema;
237 use std::path::Path;
238 use std::path::PathBuf;
239
240 fn fixture_path(relative_path: &str) -> PathBuf {
241 Path::new("tests/fixtures").join(relative_path)
242 }
243
244 #[test]
245 fn test_get_all_context() {
246 let path = fixture_path("_ro-crate-metadata-complex-context.json");
247 let rocrate = read_crate(&path, 0).unwrap();
248
249 let mut context = rocrate.context.get_all_context();
250 context.sort();
251
252 let mut fixture_vec = vec![
253 "https://w3id.org/ro/crate/1.1/context",
254 "https://criminalcharacters.com/vocab#education",
255 "https://w3id.org/ro/terms/criminalcharacters#interests",
256 ];
257 fixture_vec.sort();
258
259 assert_eq!(context, fixture_vec);
260 }
261
262 #[test]
263 fn test_add_urn() {
264 let path = fixture_path("_ro-crate-metadata-complex-context.json");
265 let mut rocrate = read_crate(&path, 0).unwrap();
266
267 let mut context = rocrate.context.get_all_context();
268 assert_eq!(context.len(), 3);
269
270 rocrate.context.add_urn_uuid();
271 context = rocrate.context.get_all_context();
272
273 assert_eq!(context.len(), 4);
274 }
275
276 #[test]
277 fn test_get_context_from_key() {
278 let path = fixture_path("_ro-crate-metadata-complex-context.json");
279 let rocrate = read_crate(&path, 0).unwrap();
280
281 let specific_context = rocrate.context.get_specific_context("education");
282
283 assert_eq!(
284 specific_context.unwrap(),
285 "https://criminalcharacters.com/vocab#education"
286 );
287 }
288
289 #[test]
290 fn test_schema_version() {
291 let path = fixture_path("_ro-crate-metadata-complex-context.json");
292 let rocrate = read_crate(&path, 0).unwrap();
293
294 let schema_version = rocrate.context.get_schema_version();
295
296 assert_eq!(schema_version.unwrap(), schema::RoCrateSchemaVersion::V1_1);
297 }
298}