1#[cfg(feature = "std")]
7use crate::error::ApiResult;
8#[cfg(feature = "std")]
9use crate::io::License;
10#[cfg(feature = "std")]
11use crate::io::{read_file, write_file, InputOutput};
12use crate::prelude::*;
13use crate::schema::namespaces::DATACITE_IDENTIFIER_TYPE_CONTROLLED_VOCABULARY;
14use crate::schema::standard::crosswalk::mapping::{dcat_to_datacite, huwise_to_datacite, invenio_to_datacite};
15use crate::schema::standard::crosswalk::{self, CrosswalkError, FieldValue, Fields, SchemaBuilder, SchemaExtractor};
16use crate::schema::standard::{dcat, huwise, invenio};
17use crate::schema::validate::{is_doi, is_iso_639_1_language_code, is_latitude, is_longitude, is_polygon, is_rfc3339, is_semantic_version, is_year};
18#[cfg(not(feature = "std"))]
19use crate::util::License;
20#[cfg(feature = "std")]
21use crate::util::MimeType;
22use crate::util::ToProse;
23#[cfg(feature = "std")]
24use crate::PathBuf;
25#[cfg(feature = "std")]
26use color_eyre::eyre::eyre;
27use core::fmt;
28use schemars::JsonSchema;
29use serde::{Deserialize, Serialize};
30use serde_with::skip_serializing_none;
31use validator::Validate;
32
33pub type Catalog = Vec<Record>;
35#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
39pub enum ContributorType {
40 ContactPerson,
42 DataCollector,
44 DataCurator,
46 DataManager,
48 Distributor,
50 Editor,
52 HostingInstitution,
54 Producer,
56 ProjectLeader,
58 ProjectManager,
62 ProjectMember,
64 RegistrationAgency,
66 RegistrationAuthority,
68 RelatedPerson,
70 ResearchGroup,
72 Researcher,
76 RightsHolder,
78 Sponsor,
80 Supervisor,
82 Translator,
84 WorkPackageLeader,
86 Other,
92}
93#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
97pub enum DateType {
98 Accepted,
100 Available,
102 Collected,
104 Copyrighted,
106 Coverage,
108 Created,
110 Issued,
112 Submitted,
114 Updated,
116 Valid,
118 Withdrawn,
120 Other,
122}
123#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
127pub enum DescriptionType {
128 Abstract,
130 Methods,
132 SeriesInformation,
134 TableOfContents,
136 TechnicalInfo,
138 Other,
140}
141#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
143pub enum FunderIdentifierType {
144 #[serde(rename = "Crossref Funder ID")]
146 CrossrefFunderId,
147 #[serde(rename = "GRID")]
151 Grid,
152 #[serde(rename = "ISNI")]
154 Isni,
155 #[serde(rename = "ROR")]
157 Ror,
158 #[serde(rename = "Other")]
160 Other,
161}
162#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
164pub enum NameType {
165 #[serde(rename = "Personal")]
167 Personal,
168 #[serde(rename = "Organizational")]
170 Organizational,
171}
172#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
176pub enum RelatedIdentifierType {
177 #[serde(rename = "ARK")]
179 Ark,
180 #[serde(rename = "arXiv")]
182 Arxiv,
183 #[serde(rename = "bibcode")]
185 Bibcode,
186 #[serde(rename = "CSTR")]
188 Cstr,
189 #[serde(rename = "DOI")]
191 Doi,
192 #[serde(rename = "EAN13")]
194 Ean13,
195 #[serde(rename = "EISSN")]
197 Eissn,
198 #[serde(rename = "Handle")]
200 Handle,
201 #[serde(rename = "IGSN")]
203 Igsn,
204 #[serde(rename = "ISBN")]
206 Isbn,
207 #[serde(rename = "ISSN")]
209 Issn,
210 #[serde(rename = "ISTC")]
212 Istc,
213 #[serde(rename = "LISSN")]
215 Lissn,
216 #[serde(rename = "LSID")]
218 Lsid,
219 #[serde(rename = "PMID")]
221 Pmid,
222 #[serde(rename = "PURL")]
224 Purl,
225 #[serde(rename = "RRID")]
227 Rrid,
228 #[serde(rename = "UPC")]
230 Upc,
231 #[serde(rename = "URL")]
233 Url,
234 #[serde(rename = "URN")]
236 Urn,
237 #[serde(rename = "w3id")]
239 W3id,
240}
241#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
247pub enum RelationType {
248 Cites,
250 Collects,
252 Compiles,
254 Continues,
256 Describes,
258 Documents,
260 HasMetadata,
262 HasPart,
264 HasTranslation,
266 HasVersion,
268 IsCitedBy,
270 IsCollectedBy,
272 IsCompiledBy,
274 IsContinuedBy,
276 IsDerivedFrom,
278 IsDescribedBy,
280 IsDocumentedBy,
282 IsIdenticalTo,
284 IsMetadataFor,
286 IsNewVersionOf,
288 IsObsoletedBy,
290 IsOriginalFormOf,
292 IsPartOf,
294 IsPreviousVersionOf,
296 IsPublishedIn,
298 IsReferencedBy,
300 IsRequiredBy,
302 IsReviewedBy,
304 IsSourceOf,
306 IsSupplementTo,
308 IsSupplementedBy,
310 IsTranslationOf,
312 IsVariantFormOf,
314 IsVersionOf,
316 Obsoletes,
318 References,
320 Requires,
322 Reviews,
324}
325#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
327pub enum ResourceTypeGeneral {
328 Audiovisual,
332 Award,
334 Book,
338 BookChapter,
342 Collection,
346 ComputationalNotebook,
350 ConferencePaper,
354 ConferenceProceeding,
358 DataPaper,
362 Dataset,
366 Dissertation,
370 Event,
374 Image,
378 InteractiveResource,
382 Instrument,
384 Journal,
388 JournalArticle,
392 Model,
394 OutputManagementPlan,
398 PeerReview,
402 PhysicalObject,
406 Preprint,
410 Project,
412 Report,
416 Service,
420 Software,
424 Sound,
428 Standard,
432 StudyRegistration,
439 Text,
443 Workflow,
445 Other,
447}
448#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
452pub enum TitleType {
453 AlternativeTitle,
455 Subtitle,
457 TranslatedTitle,
459 Other,
461}
462#[skip_serializing_none]
464#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
465pub struct Affiliation {
466 pub name: String,
468 #[serde(rename = "affiliationIdentifier")]
470 pub affiliation_identifier: Option<String>,
471 #[serde(rename = "affiliationIdentifierScheme")]
473 pub affiliation_identifier_scheme: Option<String>,
474 #[validate(url)]
476 #[serde(rename = "schemeURI")]
477 pub scheme_uri: Option<String>,
478}
479#[skip_serializing_none]
485#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
486pub struct AlternateIdentifier {
487 #[serde(rename = "alternateIdentifier")]
489 pub alternate_identifier: String,
490 #[serde(rename = "alternateIdentifierType")]
492 pub alternate_identifier_type: String,
493}
494#[skip_serializing_none]
498#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
499pub struct Attributes {
500 #[validate(custom(function = "is_doi"))]
502 pub doi: String,
503 pub event: Option<String>,
505 pub titles: Option<Vec<Title>>,
507 pub creators: Option<Vec<Creator>>,
509 pub publisher: Option<Publisher>,
511 #[validate(custom(function = "is_year"))]
515 #[serde(rename = "publicationYear")]
516 pub publication_year: Option<i32>,
517 #[serde(rename = "types")]
519 pub resource_types: Option<ResourceTypes>,
520 #[validate(url)]
522 pub url: Option<String>,
523 #[validate(nested)]
525 pub subjects: Option<Vec<Subject>>,
526 #[validate(nested)]
528 pub contributors: Option<Vec<Contributor>>,
529 #[validate(nested)]
531 pub dates: Option<Vec<Date>>,
532 #[validate(custom(function = "is_iso_639_1_language_code"))]
534 pub language: Option<String>,
535 #[serde(rename = "alternateIdentifiers")]
537 pub alternate_identifiers: Option<Vec<AlternateIdentifier>>,
538 #[validate(nested)]
540 #[serde(rename = "relatedIdentifiers")]
541 pub related_identifiers: Option<Vec<RelatedIdentifier>>,
542 pub sizes: Option<Vec<String>>,
548 pub formats: Option<Vec<String>>,
552 #[validate(custom(function = "is_semantic_version"))]
554 pub version: Option<String>,
555 #[validate(nested)]
557 #[serde(rename = "rightsList")]
558 pub rights_list: Option<Vec<Rights>>,
559 #[validate(nested)]
561 pub descriptions: Option<Vec<Description>>,
562 #[validate(nested)]
564 #[serde(rename = "geoLocations")]
565 pub geo_locations: Option<Vec<GeoLocation>>,
566 #[validate(nested)]
568 #[serde(rename = "fundingReferences")]
569 pub funding_references: Option<Vec<FundingReference>>,
570 #[validate(url)]
572 #[serde(rename = "schemaVersion")]
573 pub schema_version: Option<String>,
574}
575#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
580pub struct AwardNumber {
581 #[validate(url)]
583 #[serde(rename = "@awardURI")]
584 pub award_uri: Option<String>,
585 #[serde(rename = "$text")]
587 pub value: String,
588}
589#[skip_serializing_none]
593#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
594pub struct Contributor {
595 pub name: String,
597 #[serde(rename = "contributorType")]
599 pub contributor_type: ContributorType,
600 #[serde(rename = "nameType")]
602 pub name_type: Option<NameType>,
603 #[serde(rename = "givenName")]
605 pub given_name: Option<String>,
606 #[serde(rename = "familyName")]
608 pub family_name: Option<String>,
609 #[validate(nested)]
611 #[serde(rename = "nameIdentifiers")]
612 pub name_identifiers: Option<Vec<NameIdentifier>>,
613 #[validate(nested)]
615 pub affiliation: Option<Vec<Affiliation>>,
616}
617#[skip_serializing_none]
621#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
622pub struct Creator {
623 #[serde(alias = "creatorName")]
625 pub name: String,
626 #[serde(rename = "nameType")]
628 pub name_type: Option<NameType>,
629 #[serde(rename = "givenName")]
631 pub given_name: Option<String>,
632 #[serde(rename = "familyName")]
634 pub family_name: Option<String>,
635 #[serde(rename = "nameIdentifiers", alias = "nameIdentifier")]
637 pub name_identifiers: Option<Vec<NameIdentifier>>,
638 #[validate(nested)]
640 pub affiliation: Option<Vec<Affiliation>>,
641}
642#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
644pub struct Creators {
645 #[validate(nested)]
647 pub creator: Vec<Creator>,
648}
649#[skip_serializing_none]
653#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
654pub struct Date {
655 #[validate(custom(function = "is_rfc3339"))]
657 #[serde(alias = "$text")]
658 pub date: String,
659 #[serde(rename = "dateType", alias = "@dateType")]
661 pub date_type: DateType,
662 #[serde(rename = "dateInformation", alias = "@dateInformation")]
664 pub date_information: Option<String>,
665}
666#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
668pub struct Dates {
669 #[validate(nested)]
671 pub date: Vec<Date>,
672}
673#[skip_serializing_none]
677#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
678pub struct Description {
679 #[serde(alias = "$text")]
682 pub description: String,
683 #[serde(rename = "descriptionType", alias = "@descriptionType")]
685 pub description_type: Option<DescriptionType>,
686 #[validate(custom(function = "is_iso_639_1_language_code"))]
688 pub language: Option<String>,
689}
690#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
692pub struct Descriptions {
693 #[validate(nested)]
695 pub description: Vec<Description>,
696}
697#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
699pub struct Formats {
700 pub format: Vec<String>,
706}
707#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
712pub struct FunderIdentifier {
713 #[serde(rename = "@funderIdentifierType")]
715 pub funder_identifier_type: Option<FunderIdentifierType>,
716 #[serde(rename = "$text")]
718 pub value: String,
719}
720#[skip_serializing_none]
726#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
727pub struct FundingReference {
728 #[serde(rename = "funderName")]
730 pub funder_name: String,
731 #[serde(rename = "funderIdentifier")]
733 pub funder_identifier: Option<String>,
734 #[serde(rename = "funderIdentifierType")]
736 pub funder_identifier_type: Option<FunderIdentifierType>,
737 #[validate(url)]
739 #[serde(rename = "schemeURI")]
740 pub scheme_uri: Option<String>,
741 #[serde(rename = "awardNumber")]
743 pub award_number: Option<String>,
744 #[validate(url)]
746 #[serde(rename = "awardURI")]
747 pub award_uri: Option<String>,
748 #[serde(rename = "awardTitle")]
750 pub award_title: Option<String>,
751}
752#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
754pub struct FundingReferences {
755 #[validate(nested)]
757 #[serde(rename = "fundingReference")]
758 pub funding_reference: Vec<KernelFundingReference>,
759}
760#[skip_serializing_none]
766#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
767pub struct GeoLocation {
768 #[validate(nested)]
770 #[serde(rename = "geoLocationPoint")]
771 pub geo_location_point: Option<GeoLocationPoint>,
772 #[validate(nested)]
774 #[serde(rename = "geoLocationBox")]
775 pub geo_location_box: Option<GeoLocationBox>,
776 #[serde(rename = "geoLocationPlace")]
778 pub geo_location_place: Option<String>,
779 #[validate(nested)]
782 #[serde(rename = "geoLocationPolygon")]
783 pub geo_location_polygon: Option<GeoLocationPolygon>,
784}
785#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
787pub struct GeoLocationBox {
788 #[validate(custom(function = "is_longitude"))]
790 #[serde(rename = "westBoundLongitude")]
791 pub west_bound_longitude: f64,
792 #[validate(custom(function = "is_longitude"))]
794 #[serde(rename = "eastBoundLongitude")]
795 pub east_bound_longitude: f64,
796 #[validate(custom(function = "is_latitude"))]
798 #[serde(rename = "southBoundLatitude")]
799 pub south_bound_latitude: f64,
800 #[validate(custom(function = "is_latitude"))]
802 #[serde(rename = "northBoundLatitude")]
803 pub north_bound_latitude: f64,
804}
805#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
807pub struct GeoLocationPoint {
808 #[validate(custom(function = "is_longitude"))]
810 #[serde(rename = "pointLongitude")]
811 pub longitude: f64,
812 #[validate(custom(function = "is_latitude"))]
814 #[serde(rename = "pointLatitude")]
815 pub latitude: f64,
816}
817#[skip_serializing_none]
819#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
820pub struct GeoLocationPolygon {
821 #[validate(nested, length(min = 4), custom(function = "is_polygon"))]
823 #[serde(rename = "polygonPoints", alias = "polygonPoint")]
824 pub polygon_points: Vec<GeoLocationPoint>,
825 #[validate(nested)]
827 #[serde(rename = "inPolygonPoint")]
828 pub in_polygon_point: Option<GeoLocationPoint>,
829}
830#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
832pub struct GeoLocations {
833 #[validate(nested)]
835 #[serde(rename = "geoLocation")]
836 pub geo_location: Vec<GeoLocation>,
837}
838#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
842pub struct Identifier {
843 #[serde(default = "default_identifier_type", rename = "@identifierType")]
845 pub identifier_type: String,
846 #[validate(custom(function = "is_doi"))]
848 #[serde(rename = "$text")]
849 pub value: String,
850}
851#[skip_serializing_none]
857#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
858pub struct KernelFundingReference {
859 #[serde(rename = "funderName")]
861 pub funder_name: String,
862 #[serde(rename = "funderIdentifier")]
864 pub funder_identifier: Option<FunderIdentifier>,
865 #[validate(nested)]
867 #[serde(rename = "awardNumber")]
868 pub award_number: Option<AwardNumber>,
869 #[serde(rename = "awardTitle")]
871 pub award_title: Option<String>,
872}
873#[skip_serializing_none]
875#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
876pub struct NameIdentifier {
877 #[serde(rename = "nameIdentifier", alias = "$text")]
879 pub name_identifier: String,
880 #[serde(rename = "nameIdentifierScheme", alias = "@nameIdentifierScheme")]
882 pub name_identifier_scheme: Option<String>,
883 #[validate(url)]
885 #[serde(rename = "schemeUri", alias = "@schemeURI")]
886 pub scheme_uri: Option<String>,
887}
888#[skip_serializing_none]
892#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
893pub struct Publisher {
894 pub name: String,
896 #[serde(rename = "publisherIdentifier")]
898 pub publisher_identifier: Option<String>,
899 #[serde(rename = "publisherIdentifierScheme")]
901 pub publisher_identifier_scheme: Option<String>,
902 #[validate(url)]
904 #[serde(rename = "schemeURI")]
905 pub scheme_uri: Option<String>,
906}
907#[skip_serializing_none]
909#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
910pub struct Record {
911 #[validate(custom(function = "is_doi"))]
913 pub id: String,
914 #[serde(rename = "type")]
916 pub kind: String,
917 #[validate(nested)]
919 pub attributes: Attributes,
920}
921#[skip_serializing_none]
927#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
928pub struct RelatedIdentifier {
929 #[serde(rename = "relatedIdentifier", alias = "$text")]
931 pub related_identifier: String,
932 #[serde(rename = "relatedIdentifierType", alias = "@relatedIdentifierType")]
934 pub related_identifier_type: Option<RelatedIdentifierType>,
935 #[serde(rename = "relationType", alias = "@relationType")]
937 pub relation_type: Option<RelationType>,
938 #[serde(rename = "relatedMetadataScheme")]
940 pub related_metadata_scheme: Option<String>,
941 #[validate(url)]
943 #[serde(rename = "schemeURI")]
944 pub scheme_uri: Option<String>,
945 #[serde(rename = "schemeType")]
947 pub scheme_type: Option<String>,
948 #[serde(rename = "resourceTypeGeneral")]
950 pub resource_type_general: Option<ResourceTypeGeneral>,
951}
952#[skip_serializing_none]
954#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
955pub struct RelatedIdentifiers {
956 #[validate(nested)]
958 #[serde(rename = "relatedIdentifier")]
959 pub related_identifier: Vec<RelatedIdentifier>,
960}
961#[skip_serializing_none]
965#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
966pub struct Resource {
967 #[validate(nested)]
969 pub identifier: Identifier,
970 #[validate(nested)]
972 pub creators: Creators,
973 #[validate(nested)]
975 pub titles: Titles,
976 pub publisher: String,
978 #[validate(custom(function = "is_year"))]
982 #[serde(rename = "publicationYear")]
983 pub publication_year: u32,
984 #[serde(rename = "resourceType")]
986 pub resource_type: ResourceTypes,
987 #[validate(nested)]
989 pub subjects: Option<Subjects>,
990 #[validate(nested)]
992 pub dates: Option<Dates>,
993 #[validate(custom(function = "is_iso_639_1_language_code"))]
997 pub language: Option<String>,
998 #[serde(rename = "relatedIdentifiers")]
1000 pub related_identifiers: Option<RelatedIdentifiers>,
1001 #[validate(nested)]
1003 pub sizes: Option<Sizes>,
1004 #[validate(nested)]
1006 pub formats: Option<Formats>,
1007 #[validate(custom(function = "is_semantic_version"))]
1011 pub version: Option<String>,
1012 #[validate(nested)]
1014 #[serde(rename = "rightsList")]
1015 pub rights_list: Option<RightsList>,
1016 #[validate(nested)]
1018 pub descriptions: Option<Descriptions>,
1019 #[validate(nested)]
1021 #[serde(rename = "geoLocations")]
1022 pub geo_locations: Option<GeoLocations>,
1023 #[validate(nested)]
1025 #[serde(rename = "fundingReferences")]
1026 pub funding_references: Option<FundingReferences>,
1027}
1028#[skip_serializing_none]
1032#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1033pub struct ResourceTypes {
1034 #[serde(rename = "resourceTypeGeneral", alias = "@resourceTypeGeneral")]
1036 pub resource_type_general: Option<ResourceTypeGeneral>,
1037 #[serde(rename = "resourceType", alias = "$text")]
1039 pub resource_type: Option<String>,
1040}
1041#[skip_serializing_none]
1045#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1046pub struct Rights {
1047 #[serde(alias = "$text")]
1049 pub rights: Option<String>,
1050 #[validate(url)]
1052 #[serde(rename = "rightsURI", alias = "@rightsURI")]
1053 pub rights_uri: Option<String>,
1054 #[validate(nested)]
1056 #[serde(rename = "rightsIdentifier", alias = "@rightsIdentifier")]
1057 pub rights_identifier: Option<License>,
1058 #[serde(rename = "rightsIdentifierScheme", alias = "@rightsIdentifierScheme")]
1060 pub rights_identifier_scheme: Option<String>,
1061 #[validate(url)]
1063 #[serde(rename = "schemeURI", alias = "@schemeURI")]
1064 pub scheme_uri: Option<String>,
1065}
1066#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1068pub struct RightsList {
1069 #[validate(nested)]
1071 pub rights: Vec<Rights>,
1072}
1073#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1075pub struct Sizes {
1076 pub size: Vec<String>,
1082}
1083#[skip_serializing_none]
1087#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1088pub struct Subject {
1089 #[serde(alias = "$text")]
1091 pub subject: String,
1092 #[validate(custom(function = "is_iso_639_1_language_code"))]
1094 pub language: Option<String>,
1095 #[serde(rename = "subjectScheme", alias = "@subjectScheme")]
1097 pub subject_scheme: Option<String>,
1098 #[validate(url)]
1100 #[serde(rename = "schemeURI", alias = "@schemeURI")]
1101 pub scheme_uri: Option<String>,
1102 #[validate(url)]
1104 #[serde(rename = "valueURI", alias = "@valueURI")]
1105 pub value_uri: Option<String>,
1106 #[serde(rename = "classificationCode", alias = "@classificationCode")]
1108 pub classification_code: Option<String>,
1109}
1110#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1112pub struct Subjects {
1113 #[validate(nested)]
1115 pub subject: Vec<Subject>,
1116}
1117#[skip_serializing_none]
1121#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1122pub struct Title {
1123 #[serde(alias = "$text")]
1125 pub title: String,
1126 #[serde(rename = "titleType", alias = "@titleType")]
1128 pub title_type: Option<TitleType>,
1129}
1130#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
1132pub struct Titles {
1133 pub title: Vec<Title>,
1135}
1136impl PartialEq for GeoLocationPoint {
1137 fn eq(&self, other: &Self) -> bool {
1138 self.longitude.to_bits() == other.longitude.to_bits() && self.latitude.to_bits() == other.latitude.to_bits()
1139 }
1140}
1141impl Eq for GeoLocationPoint {}
1142impl fmt::Display for GeoLocationPoint {
1143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1144 let Self { longitude, latitude } = self;
1145 write!(f, "({longitude}, {latitude})")
1146 }
1147}
1148impl TryFrom<huwise::Dataset> for Record {
1149 type Error = CrosswalkError;
1150
1151 fn try_from(dataset: huwise::Dataset) -> Result<Self, Self::Error> {
1152 let mapping = huwise_to_datacite();
1153 crosswalk::convert(&dataset, &mapping).map(|(record, _)| record)
1154 }
1155}
1156impl TryFrom<dcat::Dataset> for Record {
1157 type Error = CrosswalkError;
1158
1159 fn try_from(dataset: dcat::Dataset) -> Result<Self, Self::Error> {
1160 let mapping = dcat_to_datacite();
1161 crosswalk::convert(&dataset, &mapping).map(|(record, _)| record)
1162 }
1163}
1164impl TryFrom<&dcat::Dataset> for Record {
1165 type Error = CrosswalkError;
1166
1167 fn try_from(dataset: &dcat::Dataset) -> Result<Self, Self::Error> {
1168 Record::try_from(dataset.clone())
1169 }
1170}
1171impl TryFrom<invenio::Record> for Record {
1172 type Error = CrosswalkError;
1173
1174 fn try_from(record: invenio::Record) -> Result<Self, Self::Error> {
1175 let mapping = invenio_to_datacite();
1176 crosswalk::convert(&record, &mapping).map(|(record, _)| record)
1177 }
1178}
1179impl TryFrom<&invenio::Record> for Record {
1180 type Error = CrosswalkError;
1181
1182 fn try_from(record: &invenio::Record) -> Result<Self, Self::Error> {
1183 Record::try_from(record.clone())
1184 }
1185}
1186impl SchemaBuilder for Record {
1187 fn build_from_fields(fields: &Fields) -> Result<Self, CrosswalkError> {
1188 let doi = fields.get_string("doi").unwrap_or_default();
1189 let mut titles = None;
1190 if let Some(title_str) = fields.get_string_opt("title") {
1191 titles = Some(vec![Title {
1192 title: title_str,
1193 title_type: None,
1194 }]);
1195 }
1196 let mut descriptions = None;
1197 if let Some(desc_str) = fields.get_string_opt("description") {
1198 descriptions = Some(vec![Description {
1199 description: desc_str,
1200 description_type: None,
1201 language: None,
1202 }]);
1203 }
1204 let language = fields.get_string_opt("language");
1205 let mut creators = None;
1206 if let Some(creator_names) = fields.get_string_vec_opt("creators") {
1207 creators = Some(
1208 creator_names
1209 .into_iter()
1210 .map(|name| Creator {
1211 name,
1212 name_type: None,
1213 given_name: None,
1214 family_name: None,
1215 name_identifiers: None,
1216 affiliation: None,
1217 })
1218 .collect(),
1219 );
1220 }
1221 let mut publisher = None;
1222 if let Some(pub_name) = fields.get_string_opt("publisher") {
1223 publisher = Some(Publisher {
1224 name: pub_name,
1225 publisher_identifier: None,
1226 publisher_identifier_scheme: None,
1227 scheme_uri: None,
1228 });
1229 }
1230 let publication_year = fields.get_number_opt("publication-year").map(|n| n as i32);
1231 let mut attributes = Attributes {
1232 doi,
1233 event: None,
1234 titles,
1235 creators,
1236 publisher,
1237 publication_year,
1238 resource_types: None,
1239 url: fields.get_iri_opt("url"),
1240 subjects: None,
1241 contributors: None,
1242 dates: None,
1243 language,
1244 alternate_identifiers: None,
1245 related_identifiers: None,
1246 sizes: None,
1247 formats: None,
1248 version: fields.get_string_opt("version"),
1249 rights_list: None,
1250 descriptions,
1251 geo_locations: None,
1252 funding_references: None,
1253 schema_version: None,
1254 };
1255 if let Some(license_iri) = fields.get_iri_opt("license") {
1256 attributes.rights_list = Some(vec![Rights {
1257 rights: None,
1258 rights_uri: Some(license_iri),
1259 rights_identifier: None,
1260 rights_identifier_scheme: None,
1261 scheme_uri: None,
1262 }]);
1263 }
1264 Ok(Record {
1265 id: attributes.doi.clone(),
1266 kind: "dois".to_string(),
1267 attributes,
1268 })
1269 }
1270}
1271impl SchemaExtractor for Record {
1272 fn extract_fields(&self) -> Fields {
1273 let mut fields = Fields::new();
1274 fields.insert("doi", FieldValue::String(self.attributes.doi.clone()));
1275 if let Some(titles) = &self.attributes.titles {
1276 if let Some(first) = titles.first() {
1277 fields.insert("title", FieldValue::String(first.title.clone()));
1278 if titles.len() > 1 {
1279 let alt_titles: Vec<String> = titles.iter().skip(1).map(|t| t.title.clone()).collect();
1280 fields.insert("alternative-titles", FieldValue::StringVec(alt_titles));
1281 }
1282 }
1283 }
1284 if let Some(descriptions) = &self.attributes.descriptions {
1285 if let Some(first) = descriptions.first() {
1286 fields.insert("description", FieldValue::String(first.description.clone()));
1287 }
1288 }
1289 if let Some(language) = &self.attributes.language {
1290 fields.insert("language", FieldValue::String(language.clone()));
1291 }
1292 if let Some(creators) = &self.attributes.creators {
1293 if !creators.is_empty() {
1294 let creator_names: Vec<String> = creators.iter().map(|c| c.name.clone()).collect();
1295 fields.insert("creators", FieldValue::StringVec(creator_names));
1296 }
1297 }
1298 if let Some(publisher) = &self.attributes.publisher {
1299 fields.insert("publisher", FieldValue::String(publisher.name.clone()));
1300 }
1301
1302 if let Some(pub_year) = self.attributes.publication_year {
1303 fields.insert("publication-year", FieldValue::Number(pub_year as f64));
1304 }
1305 if let Some(url) = &self.attributes.url {
1306 fields.insert("url", FieldValue::IRI(url.clone()));
1307 }
1308 if let Some(rights) = &self.attributes.rights_list {
1309 if let Some(first) = rights.first() {
1310 if let Some(uri) = &first.rights_uri {
1311 fields.insert("license", FieldValue::IRI(uri.clone()));
1312 }
1313 }
1314 }
1315 if let Some(identifiers) = &self.attributes.alternate_identifiers {
1316 if !identifiers.is_empty() {
1317 let alt_ids: Vec<String> = identifiers.iter().map(|id| id.alternate_identifier.clone()).collect();
1318 fields.insert("alternate-identifiers", FieldValue::StringVec(alt_ids));
1319 }
1320 }
1321 if let Some(subjects) = &self.attributes.subjects {
1322 if !subjects.is_empty() {
1323 let subject_strings: Vec<String> = subjects.iter().map(|s| s.subject.clone()).collect();
1324 fields.insert("subjects", FieldValue::StringVec(subject_strings));
1325 }
1326 }
1327 if let Some(version) = &self.attributes.version {
1328 fields.insert("version", FieldValue::String(version.clone()));
1329 }
1330 fields
1331 }
1332}
1333impl ToProse for Record {
1334 fn to_prose(&self) -> String {
1335 self.attributes
1336 .titles
1337 .iter()
1338 .flatten()
1339 .map(|value| value.title.clone())
1340 .chain(self.attributes.descriptions.iter().flatten().map(|value| value.description.clone()))
1341 .chain(self.attributes.subjects.iter().flatten().map(|value| value.subject.clone()))
1342 .collect::<Vec<String>>()
1343 .join("\n\n")
1344 }
1345}
1346#[cfg(feature = "std")]
1347impl InputOutput for Record {
1348 fn read(path: impl Into<PathBuf>) -> ApiResult<Record> {
1349 let source = path.into();
1350 match MimeType::from(source.display().to_string()) {
1351 | MimeType::Json => Record::read_json(source),
1352 | MimeType::Yaml => Record::read_yaml(source),
1353 | _ => Err(eyre!("Unsupported DataCite data file extension")),
1354 }
1355 }
1356 fn read_json(path: PathBuf) -> ApiResult<Record> {
1357 #[derive(Deserialize)]
1358 #[serde(untagged)]
1359 enum JsonInput {
1360 One(Box<Record>),
1361 Many(Vec<Record>),
1362 }
1363
1364 read_file(path).and_then(|content| {
1365 serde_json::from_str::<JsonInput>(&content)
1366 .map_err(|why| eyre!("Failed to parse JSON DataCite record — {why}"))
1367 .and_then(|value| match value {
1368 | JsonInput::One(record) => Ok(*record),
1369 | JsonInput::Many(records) => match records.len() {
1370 | 1 => records
1371 .into_iter()
1372 .next()
1373 .ok_or_else(|| eyre!("Expected one DataCite record but found none")),
1374 | len => Err(eyre!("Expected one DataCite record but found {len}")),
1375 },
1376 })
1377 })
1378 }
1379 fn read_yaml(path: PathBuf) -> ApiResult<Record> {
1380 #[derive(Deserialize)]
1381 #[serde(untagged)]
1382 enum YamlInput {
1383 One(Box<Record>),
1384 Many(Vec<Record>),
1385 }
1386
1387 read_file(path).and_then(|content| {
1388 serde_norway::from_str::<YamlInput>(&content)
1389 .map_err(|why| eyre!("Failed to parse YAML DataCite record — {why}"))
1390 .and_then(|value| match value {
1391 | YamlInput::One(record) => Ok(*record),
1392 | YamlInput::Many(records) => match records.len() {
1393 | 1 => records
1394 .into_iter()
1395 .next()
1396 .ok_or_else(|| eyre!("Expected one DataCite record but found none")),
1397 | len => Err(eyre!("Expected one DataCite record but found {len}")),
1398 },
1399 })
1400 })
1401 }
1402 fn write(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
1403 let output = path.into();
1404 match MimeType::from(output.display().to_string()) {
1405 | MimeType::Json => self.write_json(output),
1406 | MimeType::Yaml => self.write_yaml(output),
1407 | _ => Err(eyre!("Unsupported DataCite data file extension for writing")),
1408 }
1409 }
1410 fn write_json(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
1411 let output = path.into().with_extension("json");
1412 serde_json::to_string_pretty(self)
1413 .map_err(|why| eyre!("Failed to serialize JSON DataCite record — {why}"))
1414 .and_then(|content| write_file(output, content))
1415 }
1416 fn write_yaml(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
1417 let output = path.into().with_extension("yaml");
1418 serde_norway::to_string(self)
1419 .map_err(|why| eyre!("Failed to serialize YAML DataCite record — {why}"))
1420 .and_then(|content| write_file(output, content))
1421 }
1422}
1423fn default_identifier_type() -> String {
1424 DATACITE_IDENTIFIER_TYPE_CONTROLLED_VOCABULARY[0].to_string()
1425}