1#[cfg(feature = "std")]
13use crate::error::ApiResult;
14#[cfg(feature = "std")]
15use crate::io::{read_file, write_file, InputOutput};
16use crate::prelude::*;
17use crate::schema::standard::crosswalk::{self, mapping::datacite_to_dcat, CrosswalkError, FieldValue, Fields, SchemaBuilder, SchemaExtractor};
18use crate::schema::standard::datacite::{self, RelationType};
19use crate::schema::validate::is_url;
20use crate::schema::{Date as PeriodOfTime, OneOrMany};
21#[cfg(feature = "std")]
22use crate::util::MimeType;
23use crate::util::{Checksum, ToProse};
24#[cfg(feature = "std")]
25use crate::PathBuf;
26#[cfg(feature = "std")]
27use color_eyre::eyre::eyre;
28use schemars::JsonSchema;
29use serde::{Deserialize, Serialize};
30use serde_with::skip_serializing_none;
31use validator::Validate;
32
33pub(crate) mod validate;
34use validate::{is_document_refs_urls, is_one_or_many_urls};
35
36pub type DatasetSeries = Dataset;
44#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
46#[serde(untagged)]
47pub enum ConformsTo {
48 Uri(String),
50 Standard(ConformsToStandard),
52}
53#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
55#[serde(untagged)]
56pub enum ContactPoint {
57 Uri(String),
59 Kind(Kind),
61}
62#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
64#[serde(untagged)]
65pub enum DocumentRef {
66 Uri(String),
68 Document(Document),
70}
71#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
73#[serde(untagged)]
74pub enum Publisher {
75 Organization(UsOrganization),
77 Agent(Agent),
79}
80#[skip_serializing_none]
84#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
85pub struct Agent {
86 pub name: Option<String>,
88 #[serde(default)]
90 #[validate(custom(function = "is_one_or_many_urls"))]
91 pub homepage: Option<OneOrMany<String>>,
92 pub email: Option<String>,
94 #[serde(default)]
96 pub identifier: Option<OneOrMany<String>>,
97}
98#[skip_serializing_none]
102#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
103pub struct Catalog {
104 #[serde(rename = "@id")]
106 pub id: Option<String>,
107 #[serde(rename = "@type")]
109 pub jsonld_type: Option<String>,
110 #[serde(default)]
112 pub title: Option<OneOrMany<String>>,
113 #[serde(default)]
115 pub description: Option<OneOrMany<String>>,
116 #[serde(default)]
118 pub identifier: Option<OneOrMany<String>>,
119 pub issued: Option<String>,
121 pub modified: Option<String>,
123 #[serde(default)]
125 pub language: Option<OneOrMany<String>>,
126 #[validate(nested)]
128 pub publisher: Option<Publisher>,
129 #[validate(nested)]
131 pub creator: Option<Vec<Agent>>,
132 #[serde(rename = "contactPoint", default)]
134 #[validate(nested)]
135 pub contact_point: Option<OneOrMany<ContactPoint>>,
136 #[serde(default)]
138 pub keywords: Option<OneOrMany<String>>,
139 #[serde(default)]
141 pub themes: Option<OneOrMany<String>>,
142 pub license: Option<String>,
144 pub rights: Option<String>,
146 #[serde(rename = "accessRights")]
148 pub access_rights: Option<String>,
149 #[serde(rename = "hasPolicy")]
151 pub has_policy: Option<String>,
152 #[serde(rename = "conformsTo", default)]
154 #[validate(nested)]
155 pub conforms_to: Option<OneOrMany<ConformsTo>>,
156 #[serde(rename = "landingPage", default)]
158 #[validate(custom(function = "is_document_refs_urls"), nested)]
159 pub landing_page: Option<OneOrMany<DocumentRef>>,
160 #[serde(default)]
162 pub relation: Option<OneOrMany<String>>,
163 #[serde(rename = "type", default)]
165 pub type_: Option<OneOrMany<String>>,
166 pub version: Option<String>,
168 #[serde(rename = "versionNotes")]
170 pub version_notes: Option<String>,
171 #[serde(rename = "previousVersion")]
173 pub previous_version: Option<String>,
174 #[serde(rename = "hasVersion", default)]
176 pub has_version: Option<OneOrMany<String>>,
177 #[serde(rename = "hasCurrentVersion")]
179 pub has_current_version: Option<String>,
180 pub replaces: Option<String>,
182 pub status: Option<String>,
184 #[serde(rename = "isReferencedBy", default)]
186 pub is_referenced_by: Option<OneOrMany<String>>,
187 #[validate(nested)]
189 #[serde(rename = "qualifiedRelation")]
190 pub qualified_relation: Option<Vec<Relationship>>,
191 #[validate(nested)]
193 pub distribution: Option<Vec<Distribution>>,
194 pub frequency: Option<String>,
196 #[validate(nested)]
198 pub spatial: Option<Vec<Location>>,
199 #[serde(rename = "spatialResolutionInMeters")]
201 pub spatial_resolution_in_meters: Option<f64>,
202 #[validate(nested)]
204 pub temporal: Option<Vec<PeriodOfTime>>,
205 #[serde(rename = "temporalResolution")]
207 pub temporal_resolution: Option<String>,
208 #[serde(rename = "wasGeneratedBy", default)]
210 pub was_generated_by: Option<OneOrMany<String>>,
211 #[serde(default)]
213 #[validate(custom(function = "is_document_refs_urls"), nested)]
214 pub homepage: Option<OneOrMany<DocumentRef>>,
215 #[serde(rename = "themeTaxonomy", default)]
217 pub theme_taxonomy: Option<OneOrMany<String>>,
218 #[serde(default)]
220 pub dataset: Option<OneOrMany<String>>,
221 #[validate(nested)]
223 pub service: Option<Vec<DataService>>,
224 #[serde(default)]
226 pub catalog: Option<OneOrMany<String>>,
227 #[validate(nested)]
229 pub record: Option<Vec<CatalogRecord>>,
230}
231#[skip_serializing_none]
236#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
237pub struct CatalogRecord {
238 pub title: Option<String>,
240 pub description: Option<String>,
242 pub issued: Option<String>,
244 pub modified: Option<String>,
246 #[serde(rename = "primaryTopic")]
248 pub primary_topic: String,
249 #[serde(rename = "conformsTo", default)]
251 pub conforms_to: Option<OneOrMany<String>>,
252}
253#[skip_serializing_none]
255#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
256pub struct ConformsToStandard {
257 #[serde(rename = "@id")]
259 #[validate(custom(function = "is_url"))]
260 pub id: Option<String>,
261 #[serde(rename = "@type")]
263 pub jsonld_type: Option<String>,
264 pub title: Option<String>,
266 pub identifier: Option<String>,
268}
269#[skip_serializing_none]
274#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
275pub struct DataService {
276 #[serde(rename = "@id")]
278 pub id: Option<String>,
279 #[serde(rename = "@type")]
281 pub jsonld_type: Option<String>,
282 #[serde(default)]
284 pub title: Option<OneOrMany<String>>,
285 #[serde(default)]
287 pub description: Option<OneOrMany<String>>,
288 #[serde(default)]
290 pub identifier: Option<OneOrMany<String>>,
291 pub issued: Option<String>,
293 pub modified: Option<String>,
295 #[serde(default)]
297 pub language: Option<OneOrMany<String>>,
298 #[validate(nested)]
300 pub publisher: Option<Publisher>,
301 #[validate(nested)]
303 pub creator: Option<Vec<Agent>>,
304 #[serde(rename = "contactPoint", default)]
306 #[validate(nested)]
307 pub contact_point: Option<OneOrMany<ContactPoint>>,
308 #[serde(default)]
310 pub keywords: Option<OneOrMany<String>>,
311 #[serde(default)]
313 pub themes: Option<OneOrMany<String>>,
314 pub license: Option<String>,
316 pub rights: Option<String>,
318 #[serde(rename = "accessRights")]
320 pub access_rights: Option<String>,
321 #[serde(rename = "hasPolicy")]
323 pub has_policy: Option<String>,
324 #[serde(rename = "conformsTo", default)]
326 #[validate(nested)]
327 pub conforms_to: Option<OneOrMany<ConformsTo>>,
328 #[serde(rename = "landingPage", default)]
330 #[validate(custom(function = "is_document_refs_urls"), nested)]
331 pub landing_page: Option<OneOrMany<DocumentRef>>,
332 #[serde(rename = "type", default)]
334 pub type_: Option<OneOrMany<String>>,
335 pub version: Option<String>,
337 #[serde(rename = "versionNotes")]
339 pub version_notes: Option<String>,
340 #[serde(rename = "previousVersion")]
342 pub previous_version: Option<String>,
343 #[serde(rename = "hasVersion", default)]
345 pub has_version: Option<OneOrMany<String>>,
346 #[serde(rename = "hasCurrentVersion")]
348 pub has_current_version: Option<String>,
349 pub replaces: Option<String>,
351 pub status: Option<String>,
353 #[validate(nested)]
355 #[serde(rename = "qualifiedRelation")]
356 pub qualified_relation: Option<Vec<Relationship>>,
357 #[serde(rename = "endpointURL")]
359 #[validate(custom(function = "is_one_or_many_urls"))]
360 pub endpoint_url: OneOrMany<String>,
361 #[serde(rename = "endpointDescription", default)]
363 pub endpoint_description: Option<OneOrMany<String>>,
364 #[serde(rename = "servesDataset", default)]
366 pub serves_dataset: Option<OneOrMany<String>>,
367}
368#[skip_serializing_none]
373#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
374pub struct Dataset {
375 #[serde(rename = "@id")]
377 pub id: Option<String>,
378 #[serde(rename = "@type")]
380 pub jsonld_type: Option<String>,
381 #[serde(default)]
383 pub title: Option<OneOrMany<String>>,
384 #[serde(default)]
386 pub description: Option<OneOrMany<String>>,
387 #[serde(default)]
389 pub identifier: Option<OneOrMany<String>>,
390 pub issued: Option<String>,
392 pub modified: Option<String>,
394 #[serde(default)]
396 pub language: Option<OneOrMany<String>>,
397 #[validate(nested)]
399 pub publisher: Option<Publisher>,
400 #[validate(nested)]
402 pub creator: Option<Vec<Agent>>,
403 #[serde(rename = "contactPoint", default)]
405 #[validate(nested)]
406 pub contact_point: Option<OneOrMany<ContactPoint>>,
407 #[serde(default)]
409 pub keywords: Option<OneOrMany<String>>,
410 #[serde(default)]
412 pub themes: Option<OneOrMany<String>>,
413 pub license: Option<String>,
415 pub rights: Option<String>,
417 #[serde(rename = "accessRights")]
419 pub access_rights: Option<String>,
420 #[serde(rename = "hasPolicy")]
422 pub has_policy: Option<String>,
423 #[serde(rename = "conformsTo", default)]
425 #[validate(nested)]
426 pub conforms_to: Option<OneOrMany<ConformsTo>>,
427 #[serde(rename = "landingPage", default)]
429 #[validate(custom(function = "is_document_refs_urls"), nested)]
430 pub landing_page: Option<OneOrMany<DocumentRef>>,
431 #[serde(default)]
433 pub relation: Option<OneOrMany<String>>,
434 #[serde(rename = "type", default)]
436 pub type_: Option<OneOrMany<String>>,
437 pub version: Option<String>,
439 #[serde(rename = "versionNotes")]
441 pub version_notes: Option<String>,
442 #[serde(rename = "previousVersion")]
444 pub previous_version: Option<String>,
445 #[serde(rename = "hasVersion", default)]
447 pub has_version: Option<OneOrMany<String>>,
448 #[serde(rename = "hasCurrentVersion")]
450 pub has_current_version: Option<String>,
451 pub replaces: Option<String>,
453 pub status: Option<String>,
455 #[serde(rename = "isReferencedBy", default)]
457 pub is_referenced_by: Option<OneOrMany<String>>,
458 #[serde(rename = "hasPart", default)]
460 pub has_part: Option<OneOrMany<String>>,
461 #[validate(nested)]
463 #[serde(rename = "qualifiedRelation")]
464 pub qualified_relation: Option<Vec<Relationship>>,
465 pub first: Option<String>,
467 pub last: Option<String>,
469 pub previous: Option<String>,
471 #[validate(nested)]
473 pub distribution: Option<Vec<Distribution>>,
474 pub frequency: Option<String>,
476 #[serde(rename = "inSeries", default)]
478 pub in_series: Option<OneOrMany<String>>,
479 #[validate(nested)]
481 pub spatial: Option<Vec<Location>>,
482 #[serde(rename = "spatialResolutionInMeters")]
484 pub spatial_resolution_in_meters: Option<f64>,
485 #[validate(nested)]
487 pub temporal: Option<Vec<PeriodOfTime>>,
488 #[serde(rename = "temporalResolution")]
490 pub temporal_resolution: Option<String>,
491 #[serde(rename = "wasGeneratedBy", default)]
493 pub was_generated_by: Option<OneOrMany<String>>,
494}
495#[skip_serializing_none]
499#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
500pub struct Distribution {
501 #[serde(rename = "@id")]
503 pub id: Option<String>,
504 #[serde(rename = "@type")]
506 pub jsonld_type: Option<String>,
507 #[serde(default)]
509 pub title: Option<OneOrMany<String>>,
510 #[serde(default)]
512 pub description: Option<OneOrMany<String>>,
513 pub issued: Option<String>,
515 pub modified: Option<String>,
517 pub license: Option<String>,
519 #[serde(rename = "accessRights")]
521 pub access_rights: Option<String>,
522 pub rights: Option<String>,
524 #[serde(rename = "hasPolicy")]
526 pub has_policy: Option<String>,
527 #[serde(rename = "accessRestriction")]
529 pub access_restriction: Option<String>,
530 #[serde(rename = "useRestriction")]
532 pub use_restriction: Option<String>,
533 #[serde(rename = "accessURL")]
535 #[validate(custom(function = "is_one_or_many_urls"))]
536 pub access_url: OneOrMany<String>,
537 #[serde(rename = "accessService", default)]
539 pub access_service: Option<OneOrMany<String>>,
540 #[serde(rename = "downloadURL", default)]
542 #[validate(custom(function = "is_one_or_many_urls"))]
543 pub download_url: Option<OneOrMany<String>>,
544 #[serde(rename = "byteSize")]
546 pub byte_size: Option<u64>,
547 #[serde(rename = "spatialResolutionInMeters")]
549 pub spatial_resolution_in_meters: Option<f64>,
550 #[serde(rename = "temporalResolution")]
552 pub temporal_resolution: Option<String>,
553 #[serde(rename = "conformsTo", default)]
555 #[validate(nested)]
556 pub conforms_to: Option<OneOrMany<ConformsTo>>,
557 #[serde(rename = "mediaType")]
559 pub media_type: Option<String>,
560 pub format: Option<String>,
562 #[serde(rename = "compressFormat")]
564 pub compress_format: Option<String>,
565 #[serde(rename = "packageFormat")]
567 pub package_format: Option<String>,
568 #[validate(nested)]
570 pub checksum: Option<Checksum>,
571}
572#[skip_serializing_none]
574#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
575pub struct Document {
576 #[serde(rename = "@id")]
578 #[validate(custom(function = "is_url"))]
579 pub id: Option<String>,
580 #[serde(rename = "@type")]
582 pub jsonld_type: Option<String>,
583 pub title: Option<String>,
585 #[serde(rename = "accessURL")]
587 #[validate(custom(function = "is_url"))]
588 pub access_url: Option<String>,
589}
590#[skip_serializing_none]
592#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
593pub struct Kind {
594 #[serde(rename = "@type")]
596 pub jsonld_type: Option<String>,
597 #[serde(rename = "fn")]
599 pub fn_: String,
600 #[serde(rename = "hasEmail")]
602 pub has_email: String,
603 pub tel: Option<String>,
605 #[serde(rename = "organization-name")]
607 pub organization_name: Option<String>,
608}
609#[skip_serializing_none]
613#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
614pub struct Location {
615 pub geometry: Option<String>,
617 pub bbox: Option<String>,
619 pub centroid: Option<String>,
621}
622#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
626pub struct Relationship {
627 pub relation: String,
629 #[serde(rename = "hadRole", alias = "role")]
631 pub had_role: RelationType,
632}
633#[skip_serializing_none]
635#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
636pub struct UsOrganization {
637 #[serde(rename = "@id")]
639 #[validate(custom(function = "is_url"))]
640 pub id: Option<String>,
641 #[serde(rename = "@type")]
643 pub jsonld_type: Option<String>,
644 pub name: String,
646 #[serde(rename = "prefLabel")]
648 pub pref_label: Option<String>,
649 #[serde(rename = "altLabel")]
651 pub alt_label: Option<String>,
652 #[serde(rename = "subOrganizationOf")]
654 #[validate(nested)]
655 pub sub_organization_of: Option<Box<UsOrganization>>,
656}
657impl DocumentRef {
658 pub fn url(&self) -> Option<&str> {
660 match self {
661 | Self::Uri(value) => Some(value),
662 | Self::Document(document) => document.access_url.as_deref().or(document.id.as_deref()),
663 }
664 }
665}
666#[cfg(feature = "std")]
667impl InputOutput for Dataset {
668 fn read(path: impl Into<PathBuf>) -> ApiResult<Dataset> {
669 let source = path.into();
670 match MimeType::from(source.display().to_string()) {
671 | MimeType::Json => Dataset::read_json(source),
672 | MimeType::Yaml => Dataset::read_yaml(source),
673 | _ => Err(eyre!("Unsupported DCAT data file extension")),
674 }
675 }
676 fn read_json(path: PathBuf) -> ApiResult<Dataset> {
677 read_file(path).and_then(|content| {
678 serde_json::from_str::<OneOrMany<Dataset>>(&content)
679 .map_err(|why| eyre!("Failed to parse JSON DCAT dataset — {why}"))
680 .and_then(|value| match value {
681 | OneOrMany::One(dataset) => Ok(dataset),
682 | OneOrMany::Many(datasets) => match datasets.len() {
683 | 1 => datasets
684 .into_iter()
685 .next()
686 .ok_or_else(|| eyre!("Expected one DCAT dataset but found none")),
687 | len => Err(eyre!("Expected one DCAT dataset but found {len}")),
688 },
689 })
690 })
691 }
692 fn read_yaml(path: PathBuf) -> ApiResult<Dataset> {
693 read_file(path).and_then(|content| {
694 serde_norway::from_str::<OneOrMany<Dataset>>(&content)
695 .map_err(|why| eyre!("Failed to parse YAML DCAT dataset — {why}"))
696 .and_then(|value| match value {
697 | OneOrMany::One(dataset) => Ok(dataset),
698 | OneOrMany::Many(datasets) => match datasets.len() {
699 | 1 => datasets
700 .into_iter()
701 .next()
702 .ok_or_else(|| eyre!("Expected one DCAT dataset but found none")),
703 | len => Err(eyre!("Expected one DCAT dataset but found {len}")),
704 },
705 })
706 })
707 }
708 fn write(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
709 let output = path.into();
710 match MimeType::from(output.display().to_string()) {
711 | MimeType::Json => self.write_json(output),
712 | MimeType::Yaml => self.write_yaml(output),
713 | _ => Err(eyre!("Unsupported DCAT data file extension for writing")),
714 }
715 }
716 fn write_json(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
717 let output = path.into().with_extension("json");
718 serde_json::to_string_pretty(self)
719 .map_err(|why| eyre!("Failed to serialize JSON DCAT dataset — {why}"))
720 .and_then(|content| write_file(output, content))
721 }
722 fn write_yaml(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
723 let output = path.into().with_extension("yaml");
724 serde_norway::to_string(self)
725 .map_err(|why| eyre!("Failed to serialize YAML DCAT dataset — {why}"))
726 .and_then(|content| write_file(output, content))
727 }
728}
729impl Publisher {
730 pub fn name(&self) -> Option<&str> {
732 match self {
733 | Self::Organization(organization) => Some(organization.name.as_str()),
734 | Self::Agent(agent) => agent.name.as_deref(),
735 }
736 }
737}
738impl SchemaBuilder for Dataset {
739 fn build_from_fields(fields: &Fields) -> Result<Self, CrosswalkError> {
740 let identifier = fields.get_string_vec_opt("identifier").map(OneOrMany::Many);
741 let mut title = None;
742 if let Some(title_str) = fields.get_string_opt("title") {
743 title = Some(OneOrMany::Many(vec![title_str]));
744 }
745 let mut description = None;
746 if let Some(desc_str) = fields.get_string_opt("description") {
747 description = Some(OneOrMany::Many(vec![desc_str]));
748 }
749 let issued = fields.get_date_opt("issued");
750 let language = fields.get_string_vec_opt("language").map(OneOrMany::Many);
751 let keywords = fields.get_string_vec_opt("keywords").map(OneOrMany::Many);
752 let themes = fields.get_string_vec_opt("themes").map(OneOrMany::Many);
753 let version = fields.get_string_opt("version");
754 let mut publisher = None;
755 if let Some(pub_name) = fields.get_string_opt("publisher") {
756 publisher = Some(Publisher::Agent(Agent {
757 name: Some(pub_name),
758 homepage: None,
759 email: None,
760 identifier: None,
761 }));
762 }
763 let mut creator = None;
764 if let Some(creator_names) = fields.get_string_vec_opt("creators") {
765 creator = Some(
766 creator_names
767 .into_iter()
768 .map(|name| Agent {
769 name: Some(name),
770 homepage: None,
771 email: None,
772 identifier: None,
773 })
774 .collect(),
775 );
776 }
777 let license = fields.get_iri_opt("license");
778 let landing_page = fields
779 .get_string_opt("landing_page")
780 .or_else(|| fields.get_iri_opt("landing_page"))
781 .map(|value| OneOrMany::Many(vec![DocumentRef::Uri(value)]));
782 let mut spatial = None;
783 if let Some(spatial_str) = fields.get_string_opt("spatial") {
784 spatial = Some(vec![Location {
785 geometry: Some(spatial_str),
786 bbox: None,
787 centroid: None,
788 }]);
789 }
790 Ok(Dataset {
791 id: None,
792 jsonld_type: Some("dcat:Dataset".to_string()),
793 title,
794 description,
795 identifier,
796 issued,
797 modified: None,
798 language,
799 publisher,
800 creator,
801 contact_point: None,
802 keywords,
803 themes,
804 license,
805 rights: None,
806 access_rights: None,
807 has_policy: None,
808 conforms_to: None,
809 landing_page,
810 relation: None,
811 type_: None,
812 version,
813 version_notes: None,
814 previous_version: None,
815 has_version: None,
816 has_current_version: None,
817 replaces: None,
818 status: None,
819 is_referenced_by: None,
820 has_part: None,
821 qualified_relation: None,
822 first: None,
823 last: None,
824 previous: None,
825 distribution: None,
826 frequency: None,
827 in_series: None,
828 spatial,
829 spatial_resolution_in_meters: None,
830 temporal: None,
831 temporal_resolution: None,
832 was_generated_by: None,
833 })
834 }
835}
836impl SchemaExtractor for Dataset {
837 fn extract_fields(&self) -> Fields {
838 let mut fields = Fields::new();
839 if let Some(identifiers) = &self.identifier {
840 if !identifiers.is_empty() {
841 fields.insert("identifier", FieldValue::StringVec(identifiers.as_slice().to_vec()));
842 }
843 }
844 if let Some(titles) = &self.title {
845 if let Some(first) = titles.first() {
846 fields.insert("title", FieldValue::String(first.clone()));
847 if titles.len() > 1 {
848 let alt_titles: Vec<String> = titles.iter().skip(1).cloned().collect();
849 fields.insert("alternative-titles", FieldValue::StringVec(alt_titles));
850 }
851 }
852 }
853 if let Some(descriptions) = &self.description {
854 if let Some(first) = descriptions.first() {
855 fields.insert("description", FieldValue::String(first.clone()));
856 }
857 }
858 if let Some(issued) = &self.issued {
859 fields.insert("issued", FieldValue::Date(issued.clone()));
860 }
861 if let Some(keywords) = &self.keywords {
862 fields.insert("keywords", FieldValue::StringVec(keywords.as_slice().to_vec()));
863 }
864 if let Some(themes) = &self.themes {
865 fields.insert("themes", FieldValue::StringVec(themes.as_slice().to_vec()));
866 }
867 if let Some(language) = &self.language {
868 fields.insert("language", FieldValue::StringVec(language.as_slice().to_vec()));
869 }
870 if let Some(version) = &self.version {
871 fields.insert("version", FieldValue::String(version.clone()));
872 }
873 if let Some(publisher) = &self.publisher {
874 if let Some(name) = publisher.name() {
875 fields.insert("publisher", FieldValue::String(name.to_string()));
876 }
877 }
878 if let Some(creators) = &self.creator {
879 let creator_names: Vec<String> = creators.iter().filter_map(|c| c.name.clone()).collect();
880 if !creator_names.is_empty() {
881 fields.insert("creators", FieldValue::StringVec(creator_names));
882 }
883 }
884 if let Some(license) = &self.license {
885 fields.insert("license", FieldValue::IRI(license.clone()));
886 }
887 if let Some(spatial) = &self.spatial {
888 if let Some(first) = spatial.first() {
889 if let Some(geometry) = &first.geometry {
890 fields.insert("spatial", FieldValue::String(geometry.clone()));
891 }
892 }
893 }
894 fields
895 }
896}
897impl ToProse for Dataset {
898 fn to_prose(&self) -> String {
899 self.title
900 .iter()
901 .flatten()
902 .cloned()
903 .chain(self.description.iter().flatten().cloned())
904 .chain(self.keywords.iter().flatten().cloned())
905 .collect::<Vec<String>>()
906 .join("\n\n")
907 }
908}
909impl TryFrom<&datacite::Record> for Dataset {
910 type Error = CrosswalkError;
911
912 fn try_from(record: &datacite::Record) -> Result<Self, Self::Error> {
913 Dataset::try_from(record.clone())
914 }
915}
916impl TryFrom<datacite::Record> for Dataset {
917 type Error = CrosswalkError;
918
919 fn try_from(record: datacite::Record) -> Result<Self, Self::Error> {
920 let mapping = datacite_to_dcat();
921 crosswalk::convert(&record, &mapping).map(|(dataset, _)| dataset)
922 }
923}