1use super::CrosswalkError;
6use crate::prelude::HashMap;
7use crate::prelude::*;
8use core::fmt;
9use serde_json::Value;
10
11pub type FieldTransform = fn(FieldValue) -> Result<FieldValue, String>;
15#[derive(Clone, Debug)]
19pub enum FieldValue {
20 String(String),
22 StringVec(Vec<String>),
24 Date(String),
26 IRI(String),
28 Number(f64),
30 Object(Box<Fields>),
32 ObjectVec(Vec<Fields>),
34 Json(Value),
36}
37#[derive(Clone)]
41pub struct FieldMapping {
42 pub source: &'static str,
44 pub target: &'static str,
46 pub rules: Vec<FieldRule>,
48}
49#[derive(Clone)]
54pub struct FieldRule {
55 pub source: &'static str,
57 pub target: &'static str,
59 pub transform: Option<FieldTransform>,
61 pub required: bool,
63}
64impl From<HashMap<String, FieldValue>> for Fields {
65 fn from(fields: HashMap<String, FieldValue>) -> Self {
66 Self { fields }
67 }
68}
69impl Fields {
70 pub fn new() -> Self {
72 Self { fields: HashMap::new() }
73 }
74 pub fn insert(&mut self, key: impl Into<String>, value: FieldValue) {
76 self.fields.insert(key.into(), value);
77 }
78 pub fn get(&self, key: &str) -> Option<&FieldValue> {
80 self.fields.get(key)
81 }
82 pub fn remove(&mut self, key: &str) -> Option<FieldValue> {
84 self.fields.remove(key)
85 }
86 pub fn contains_key(&self, key: &str) -> bool {
88 self.fields.contains_key(key)
89 }
90 pub fn keys(&self) -> impl Iterator<Item = &String> {
92 self.fields.keys()
93 }
94 pub fn iter(&self) -> impl Iterator<Item = (&String, &FieldValue)> {
96 self.fields.iter()
97 }
98 pub fn is_empty(&self) -> bool {
100 self.fields.is_empty()
101 }
102 pub fn len(&self) -> usize {
104 self.fields.len()
105 }
106 pub fn get_string(&self, key: &str) -> Result<String, String> {
108 self.get(key)
109 .and_then(|v| v.as_string().map(|s| s.to_string()))
110 .ok_or_else(|| format!("expected string field '{key}'"))
111 }
112 pub fn get_string_opt(&self, key: &str) -> Option<String> {
114 self.get(key).and_then(|v| v.as_string().map(|s| s.to_string()))
115 }
116 pub fn get_string_vec(&self, key: &str) -> Result<Vec<String>, String> {
118 self.get(key)
119 .and_then(|v| v.as_string_vec().map(|sv| sv.to_vec()))
120 .ok_or_else(|| format!("expected string vector field '{key}'"))
121 }
122 pub fn get_string_vec_opt(&self, key: &str) -> Option<Vec<String>> {
124 self.get(key).and_then(|v| v.as_string_vec().map(|sv| sv.to_vec()))
125 }
126 pub fn get_date(&self, key: &str) -> Result<String, String> {
128 self.get(key)
129 .and_then(|v| v.as_date().map(|d| d.to_string()))
130 .ok_or_else(|| format!("expected date field '{key}'"))
131 }
132 pub fn get_date_opt(&self, key: &str) -> Option<String> {
134 self.get(key).and_then(|v| v.as_date().map(|d| d.to_string()))
135 }
136
137 pub fn get_iri(&self, key: &str) -> Result<String, String> {
139 self.get(key)
140 .and_then(|v| v.as_iri().map(|iri| iri.to_string()))
141 .ok_or_else(|| format!("expected IRI field '{key}'"))
142 }
143 pub fn get_iri_opt(&self, key: &str) -> Option<String> {
145 self.get(key).and_then(|v| v.as_iri().map(|iri| iri.to_string()))
146 }
147 pub fn get_number(&self, key: &str) -> Result<f64, String> {
149 self.get(key)
150 .and_then(|v| v.as_number())
151 .ok_or_else(|| format!("expected number field '{key}'"))
152 }
153 pub fn get_number_opt(&self, key: &str) -> Option<f64> {
155 self.get(key).and_then(|v| v.as_number())
156 }
157 pub fn get_object(&self, key: &str) -> Result<&Fields, String> {
159 self.get(key)
160 .and_then(|v| v.as_object())
161 .ok_or_else(|| format!("expected object field '{key}'"))
162 }
163 pub fn get_object_vec(&self, key: &str) -> Result<&[Fields], String> {
165 self.get(key)
166 .and_then(|v| v.as_object_vec())
167 .ok_or_else(|| format!("expected object vector field '{key}'"))
168 }
169}
170#[derive(Clone, Debug, Default)]
175pub struct Fields {
176 fields: HashMap<String, FieldValue>,
177}
178impl FieldMapping {
179 pub fn new(source: &'static str, target: &'static str) -> Self {
181 Self {
182 source,
183 target,
184 rules: Vec::new(),
185 }
186 }
187 pub fn with_rule(mut self, rule: FieldRule) -> Self {
189 self.rules.push(rule);
190 self
191 }
192 pub fn apply(&self, source: &Fields, target: &mut Fields) -> Result<Vec<String>, CrosswalkError> {
196 let mut missing = Vec::new();
197 for rule in &self.rules {
198 if let Ok(Some(field)) = rule.apply(source, target) {
199 missing.push(field);
200 }
201 }
202 Ok(missing)
203 }
204}
205impl FieldRule {
206 pub fn new(source: &'static str, target: &'static str) -> Self {
208 Self {
209 source,
210 target,
211 transform: None,
212 required: false,
213 }
214 }
215 pub fn required_field(source: &'static str, target: &'static str) -> Self {
217 Self {
218 source,
219 target,
220 transform: None,
221 required: true,
222 }
223 }
224 pub fn map(source: &'static str, target: &'static str) -> Self {
226 Self {
227 source,
228 target,
229 transform: None,
230 required: false,
231 }
232 }
233 pub fn required(mut self) -> Self {
235 self.required = true;
236 self
237 }
238 pub fn with_transform(mut self, transform: FieldTransform) -> Self {
240 self.transform = Some(transform);
241 self
242 }
243 pub fn apply(&self, source: &Fields, target: &mut Fields) -> Result<Option<String>, CrosswalkError> {
245 match source.get(self.source) {
246 | Some(value) => {
247 let transformed = match self.transform {
248 | Some(f) => f(value.clone()).map_err(|reason| CrosswalkError::TransformationFailed {
249 field: self.source.to_string(),
250 reason,
251 })?,
252 | None => value.clone(),
253 };
254 for target_name in self.target.split('|') {
255 target.insert(target_name.trim(), transformed.clone());
256 }
257 Ok(None)
258 }
259 | None => {
260 if self.required {
261 Err(CrosswalkError::MissingRequiredField(self.source.to_string()))
262 } else {
263 Ok(Some(self.source.to_string()))
264 }
265 }
266 }
267 }
268}
269impl fmt::Display for FieldValue {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 match self {
272 | Self::String(s) => write!(f, "{s}"),
273 | Self::StringVec(v) => write!(f, "[{}]", v.join(", ")),
274 | Self::Date(d) => write!(f, "{d}"),
275 | Self::IRI(iri) => write!(f, "{iri}"),
276 | Self::Number(n) => write!(f, "{n}"),
277 | Self::Object(_) => write!(f, "{{...}}"),
278 | Self::ObjectVec(v) => write!(f, "[...{}]", v.len()),
279 | Self::Json(val) => write!(f, "{val}"),
280 }
281 }
282}
283impl From<&Fields> for HashMap<String, FieldValue> {
284 fn from(map: &Fields) -> Self {
285 map.fields.clone()
286 }
287}
288impl FieldValue {
289 pub fn as_string(&self) -> Option<&str> {
291 match self {
292 | Self::String(s) => Some(s),
293 | _ => None,
294 }
295 }
296 pub fn as_string_vec(&self) -> Option<&[String]> {
298 match self {
299 | Self::StringVec(v) => Some(v),
300 | _ => None,
301 }
302 }
303 pub fn as_date(&self) -> Option<&str> {
305 match self {
306 | Self::Date(d) => Some(d),
307 | _ => None,
308 }
309 }
310 pub fn as_iri(&self) -> Option<&str> {
312 match self {
313 | Self::IRI(iri) => Some(iri),
314 | _ => None,
315 }
316 }
317 pub fn as_number(&self) -> Option<f64> {
319 match self {
320 | Self::Number(n) => Some(*n),
321 | _ => None,
322 }
323 }
324 pub fn as_object(&self) -> Option<&Fields> {
326 match self {
327 | Self::Object(map) => Some(map),
328 | _ => None,
329 }
330 }
331 pub fn as_object_vec(&self) -> Option<&[Fields]> {
333 match self {
334 | Self::ObjectVec(v) => Some(v),
335 | _ => None,
336 }
337 }
338}
339pub fn datacite_to_dcat() -> FieldMapping {
341 FieldMapping::new("datacite", "dcat")
342 .with_rule(FieldRule::new("doi", "identifier").required())
343 .with_rule(FieldRule::new("title", "title"))
344 .with_rule(FieldRule::new("description", "description"))
345 .with_rule(FieldRule::new("language", "language").with_transform(as_vec))
346 .with_rule(FieldRule::new("creators", "creators"))
347 .with_rule(FieldRule::new("publisher", "publisher"))
348 .with_rule(FieldRule::new("publication-year", "issued").with_transform(year_to_date))
349 .with_rule(FieldRule::new("license", "license"))
350 .with_rule(FieldRule::new("url", "landing_page"))
351 .with_rule(FieldRule::new("subjects", "keywords"))
352 .with_rule(FieldRule::new("resource-type", "type_").with_transform(resource_type_to_iri))
353}
354pub fn datacite_to_huwise() -> FieldMapping {
356 FieldMapping::new("datacite", "huwise")
357 .with_rule(FieldRule::map("doi", "identifier").required())
358 .with_rule(FieldRule::map("title", "title"))
359 .with_rule(FieldRule::map("description", "description"))
360 .with_rule(FieldRule::map("creators", "creators"))
361 .with_rule(FieldRule::map("publication-year", "publication-year"))
362 .with_rule(FieldRule::map("resource-type", "resource-type"))
363 .with_rule(FieldRule::map("subjects", "subjects"))
364 .with_rule(FieldRule::map("language", "language"))
365 .with_rule(FieldRule::map("publisher", "publisher"))
366 .with_rule(FieldRule::map("license", "license"))
367 .with_rule(FieldRule::map("version", "version"))
368}
369pub fn datacite_to_invenio() -> FieldMapping {
371 FieldMapping::new("datacite", "invenio")
372 .with_rule(FieldRule::map("doi", "identifier").required())
373 .with_rule(FieldRule::map("title", "title"))
374 .with_rule(FieldRule::map("description", "description"))
375 .with_rule(FieldRule::map("language", "language"))
376 .with_rule(FieldRule::map("creators", "creators"))
377 .with_rule(FieldRule::map("publication-year", "publication-year").with_transform(extract_year))
378 .with_rule(FieldRule::map("resource-type", "resource-type"))
379 .with_rule(FieldRule::map("license", "license"))
380 .with_rule(FieldRule::map("alternate-identifiers", "alternate-identifiers"))
381 .with_rule(FieldRule::map("subjects", "subjects"))
382 .with_rule(FieldRule::map("contributors", "contributors"))
383 .with_rule(FieldRule::map("version", "version"))
384}
385pub fn dcat_to_datacite() -> FieldMapping {
387 FieldMapping::new("dcat", "datacite")
388 .with_rule(FieldRule::new("identifier", "doi").required().with_transform(first_of_vec))
389 .with_rule(FieldRule::new("title", "title"))
390 .with_rule(FieldRule::new("description", "description"))
391 .with_rule(FieldRule::new("language", "language").with_transform(first_of_vec))
392 .with_rule(FieldRule::new("creators", "creators"))
393 .with_rule(FieldRule::new("publisher", "publisher"))
394 .with_rule(FieldRule::new("issued", "publication-year").with_transform(extract_year))
395 .with_rule(FieldRule::new("license", "license"))
396 .with_rule(FieldRule::new("type_", "resource-type").with_transform(iri_to_resource_type))
397 .with_rule(FieldRule::new("keywords", "subjects"))
398 .with_rule(FieldRule::new("themes", "subjects"))
399}
400pub fn huwise_to_datacite() -> FieldMapping {
402 FieldMapping::new("huwise", "datacite")
403 .with_rule(FieldRule::map("doi", "doi").required())
404 .with_rule(FieldRule::map("title", "title"))
405 .with_rule(FieldRule::map("description", "description"))
406 .with_rule(FieldRule::map("creators", "creators"))
407 .with_rule(FieldRule::map("contributors", "contributors"))
408 .with_rule(FieldRule::map("publication-year", "publication-year"))
409 .with_rule(FieldRule::map("resource-type", "resource-type"))
410 .with_rule(FieldRule::map("subjects", "subjects"))
411 .with_rule(FieldRule::map("language", "language"))
412 .with_rule(FieldRule::map("publisher", "publisher"))
413 .with_rule(FieldRule::map("license", "license"))
414 .with_rule(FieldRule::map("version", "version"))
415}
416pub fn invenio_to_datacite() -> FieldMapping {
418 FieldMapping::new("invenio", "datacite")
419 .with_rule(FieldRule::map("identifier", "doi").required())
420 .with_rule(FieldRule::map("title", "title"))
421 .with_rule(FieldRule::map("description", "description"))
422 .with_rule(FieldRule::map("language", "language"))
423 .with_rule(FieldRule::map("creators", "creators"))
424 .with_rule(FieldRule::map("publication-year", "publication-year"))
425 .with_rule(FieldRule::map("resource-type", "resource-type"))
426 .with_rule(FieldRule::map("license", "license"))
427 .with_rule(FieldRule::map("alternate-identifiers", "alternate-identifiers"))
428 .with_rule(FieldRule::map("subjects", "subjects"))
429 .with_rule(FieldRule::map("contributors", "contributors"))
430}
431pub fn first_of_vec(value: FieldValue) -> Result<FieldValue, String> {
434 match value {
435 | FieldValue::StringVec(mut v) if !v.is_empty() => Ok(FieldValue::String(v.remove(0))),
436 | FieldValue::StringVec(_) => Err("empty string vector".to_string()),
437 | _ => Err("expected string vector".to_string()),
438 }
439}
440pub fn as_vec(value: FieldValue) -> Result<FieldValue, String> {
442 match value {
443 | FieldValue::StringVec(_) => Ok(value),
444 | FieldValue::String(s) => Ok(FieldValue::StringVec(vec![s])),
445 | _ => Err("expected string or string vector".to_string()),
446 }
447}
448pub fn extract_year(value: FieldValue) -> Result<FieldValue, String> {
450 match value {
451 | FieldValue::Date(d) => {
452 let year = d
453 .split('-')
454 .next()
455 .and_then(|y| y.parse::<i32>().ok())
456 .ok_or_else(|| format!("cannot extract year from date: {d}"))?;
457 Ok(FieldValue::Number(year as f64))
458 }
459 | FieldValue::String(s) => s
460 .parse::<i32>()
461 .map(|y| FieldValue::Number(y as f64))
462 .map_err(|_| format!("cannot parse year from string: {s}")),
463 | FieldValue::Number(n) => Ok(FieldValue::Number(n)),
464 | _ => Err("expected date, string, or number".to_string()),
465 }
466}
467pub fn year_to_date(value: FieldValue) -> Result<FieldValue, String> {
469 match value {
470 | FieldValue::Number(n) => Ok(FieldValue::Date(format!("{:04}-01-01", n as i32))),
471 | FieldValue::String(s) => {
472 let _: i32 = s.parse().map_err(|_| format!("cannot parse year: {s}"))?;
473 Ok(FieldValue::Date(format!("{s}-01-01")))
474 }
475 | _ => Err("expected number or string year".to_string()),
476 }
477}
478pub fn resource_type_to_iri(value: FieldValue) -> Result<FieldValue, String> {
480 match value {
481 | FieldValue::String(s) => {
482 let iri = match s.as_str() {
483 | "Dataset" => "http://www.w3.org/ns/dcat#Dataset",
484 | "Software" => "https://schema.org/SoftwareApplication",
485 | "Audiovisual" => "https://schema.org/VideoObject",
486 | "Book" => "https://schema.org/Book",
487 | "BookChapter" => "https://schema.org/Chapter",
488 | "ConferencePaper" => "https://schema.org/ScholarlyArticle",
489 | "Dissertation" => "https://schema.org/Thesis",
490 | "Image" => "https://schema.org/ImageObject",
491 | "Journal" => "https://schema.org/Periodical",
492 | "JournalArticle" => "https://schema.org/ScholarlyArticle",
493 | "Report" => "https://schema.org/Report",
494 | "Text" => "https://schema.org/CreativeWork",
495 | _ => return Err(format!("unknown resource type: {s}")),
496 };
497 Ok(FieldValue::IRI(iri.to_string()))
498 }
499 | _ => Err("expected string resource type".to_string()),
500 }
501}
502pub fn iri_to_resource_type(value: FieldValue) -> Result<FieldValue, String> {
504 match value {
505 | FieldValue::IRI(iri) => {
506 let type_str = match iri.as_str() {
507 | "http://www.w3.org/ns/dcat#Dataset" => "Dataset",
508 | "https://schema.org/SoftwareApplication" => "Software",
509 | "https://schema.org/VideoObject" => "Audiovisual",
510 | "https://schema.org/Book" => "Book",
511 | "https://schema.org/Chapter" => "BookChapter",
512 | "https://schema.org/ScholarlyArticle" => "JournalArticle",
513 | "https://schema.org/Thesis" => "Dissertation",
514 | "https://schema.org/ImageObject" => "Image",
515 | "https://schema.org/Periodical" => "Journal",
516 | "https://schema.org/Report" => "Report",
517 | "https://schema.org/CreativeWork" => "Text",
518 | _ => return Err(format!("unknown IRI: {iri}")),
519 };
520 Ok(FieldValue::String(type_str.to_string()))
521 }
522 | _ => Err("expected IRI resource type".to_string()),
523 }
524}