#[cfg(feature = "std")]
use crate::io::License;
#[cfg(feature = "std")]
use crate::io::{read_file, write_file, ApiResult, InputOutput};
use crate::prelude::*;
use crate::schema::namespaces::DATACITE_IDENTIFIER_TYPE_CONTROLLED_VOCABULARY;
use crate::schema::standard::crosswalk::mapping::{dcat_to_datacite, huwise_to_datacite, invenio_to_datacite};
use crate::schema::standard::crosswalk::{self, CrosswalkError, FieldValue, Fields, SchemaBuilder, SchemaExtractor};
use crate::schema::standard::{dcat, huwise, invenio};
use crate::schema::validate::{is_doi, is_iso_639_1_language_code, is_latitude, is_longitude, is_polygon, is_rfc3339, is_semantic_version, is_year};
#[cfg(not(feature = "std"))]
use crate::util::License;
#[cfg(feature = "std")]
use crate::util::MimeType;
use crate::util::ToProse;
#[cfg(feature = "std")]
use crate::PathBuf;
#[cfg(feature = "std")]
use color_eyre::eyre::eyre;
use core::fmt;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use validator::Validate;
pub type Catalog = Vec<Record>;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum ContributorType {
ContactPerson,
DataCollector,
DataCurator,
DataManager,
Distributor,
Editor,
HostingInstitution,
Producer,
ProjectLeader,
ProjectManager,
ProjectMember,
RegistrationAgency,
RegistrationAuthority,
RelatedPerson,
ResearchGroup,
Researcher,
RightsHolder,
Sponsor,
Supervisor,
Translator,
WorkPackageLeader,
Other,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum DateType {
Accepted,
Available,
Collected,
Copyrighted,
Coverage,
Created,
Issued,
Submitted,
Updated,
Valid,
Withdrawn,
Other,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum DescriptionType {
Abstract,
Methods,
SeriesInformation,
TableOfContents,
TechnicalInfo,
Other,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum FunderIdentifierType {
#[serde(rename = "Crossref Funder ID")]
CrossrefFunderId,
#[serde(rename = "GRID")]
Grid,
#[serde(rename = "ISNI")]
Isni,
#[serde(rename = "ROR")]
Ror,
#[serde(rename = "Other")]
Other,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum NameType {
#[serde(rename = "Personal")]
Personal,
#[serde(rename = "Organizational")]
Organizational,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum RelatedIdentifierType {
#[serde(rename = "ARK")]
Ark,
#[serde(rename = "arXiv")]
Arxiv,
#[serde(rename = "bibcode")]
Bibcode,
#[serde(rename = "CSTR")]
Cstr,
#[serde(rename = "DOI")]
Doi,
#[serde(rename = "EAN13")]
Ean13,
#[serde(rename = "EISSN")]
Eissn,
#[serde(rename = "Handle")]
Handle,
#[serde(rename = "IGSN")]
Igsn,
#[serde(rename = "ISBN")]
Isbn,
#[serde(rename = "ISSN")]
Issn,
#[serde(rename = "ISTC")]
Istc,
#[serde(rename = "LISSN")]
Lissn,
#[serde(rename = "LSID")]
Lsid,
#[serde(rename = "PMID")]
Pmid,
#[serde(rename = "PURL")]
Purl,
#[serde(rename = "RRID")]
Rrid,
#[serde(rename = "UPC")]
Upc,
#[serde(rename = "URL")]
Url,
#[serde(rename = "URN")]
Urn,
#[serde(rename = "w3id")]
W3id,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum RelationType {
Cites,
Collects,
Compiles,
Continues,
Describes,
Documents,
HasMetadata,
HasPart,
HasTranslation,
HasVersion,
IsCitedBy,
IsCollectedBy,
IsCompiledBy,
IsContinuedBy,
IsDerivedFrom,
IsDescribedBy,
IsDocumentedBy,
IsIdenticalTo,
IsMetadataFor,
IsNewVersionOf,
IsObsoletedBy,
IsOriginalFormOf,
IsPartOf,
IsPreviousVersionOf,
IsPublishedIn,
IsReferencedBy,
IsRequiredBy,
IsReviewedBy,
IsSourceOf,
IsSupplementTo,
IsSupplementedBy,
IsTranslationOf,
IsVariantFormOf,
IsVersionOf,
Obsoletes,
References,
Requires,
Reviews,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum ResourceTypeGeneral {
Audiovisual,
Award,
Book,
BookChapter,
Collection,
ComputationalNotebook,
ConferencePaper,
ConferenceProceeding,
DataPaper,
Dataset,
Dissertation,
Event,
Image,
InteractiveResource,
Instrument,
Journal,
JournalArticle,
Model,
OutputManagementPlan,
PeerReview,
PhysicalObject,
Preprint,
Project,
Report,
Service,
Software,
Sound,
Standard,
StudyRegistration,
Text,
Workflow,
Other,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub enum TitleType {
AlternativeTitle,
Subtitle,
TranslatedTitle,
Other,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Affiliation {
pub name: String,
#[serde(rename = "affiliationIdentifier")]
pub affiliation_identifier: Option<String>,
#[serde(rename = "affiliationIdentifierScheme")]
pub affiliation_identifier_scheme: Option<String>,
#[validate(url)]
#[serde(rename = "schemeURI")]
pub scheme_uri: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct AlternateIdentifier {
#[serde(rename = "alternateIdentifier")]
pub alternate_identifier: String,
#[serde(rename = "alternateIdentifierType")]
pub alternate_identifier_type: String,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Attributes {
#[validate(custom(function = "is_doi"))]
pub doi: String,
pub event: Option<String>,
pub titles: Option<Vec<Title>>,
pub creators: Option<Vec<Creator>>,
pub publisher: Option<Publisher>,
#[validate(custom(function = "is_year"))]
#[serde(rename = "publicationYear")]
pub publication_year: Option<i32>,
#[serde(rename = "types")]
pub resource_types: Option<ResourceTypes>,
#[validate(url)]
pub url: Option<String>,
#[validate(nested)]
pub subjects: Option<Vec<Subject>>,
#[validate(nested)]
pub contributors: Option<Vec<Contributor>>,
#[validate(nested)]
pub dates: Option<Vec<Date>>,
#[validate(custom(function = "is_iso_639_1_language_code"))]
pub language: Option<String>,
#[serde(rename = "alternateIdentifiers")]
pub alternate_identifiers: Option<Vec<AlternateIdentifier>>,
#[validate(nested)]
#[serde(rename = "relatedIdentifiers")]
pub related_identifiers: Option<Vec<RelatedIdentifier>>,
pub sizes: Option<Vec<String>>,
pub formats: Option<Vec<String>>,
#[validate(custom(function = "is_semantic_version"))]
pub version: Option<String>,
#[validate(nested)]
#[serde(rename = "rightsList")]
pub rights_list: Option<Vec<Rights>>,
#[validate(nested)]
pub descriptions: Option<Vec<Description>>,
#[validate(nested)]
#[serde(rename = "geoLocations")]
pub geo_locations: Option<Vec<GeoLocation>>,
#[validate(nested)]
#[serde(rename = "fundingReferences")]
pub funding_references: Option<Vec<FundingReference>>,
#[validate(url)]
#[serde(rename = "schemaVersion")]
pub schema_version: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct AwardNumber {
#[validate(url)]
#[serde(rename = "@awardURI")]
pub award_uri: Option<String>,
#[serde(rename = "$text")]
pub value: String,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Contributor {
pub name: String,
#[serde(rename = "contributorType")]
pub contributor_type: ContributorType,
#[serde(rename = "nameType")]
pub name_type: Option<NameType>,
#[serde(rename = "givenName")]
pub given_name: Option<String>,
#[serde(rename = "familyName")]
pub family_name: Option<String>,
#[validate(nested)]
#[serde(rename = "nameIdentifiers")]
pub name_identifiers: Option<Vec<NameIdentifier>>,
#[validate(nested)]
pub affiliation: Option<Vec<Affiliation>>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Creator {
#[serde(alias = "creatorName")]
pub name: String,
#[serde(rename = "nameType")]
pub name_type: Option<NameType>,
#[serde(rename = "givenName")]
pub given_name: Option<String>,
#[serde(rename = "familyName")]
pub family_name: Option<String>,
#[serde(rename = "nameIdentifiers", alias = "nameIdentifier")]
pub name_identifiers: Option<Vec<NameIdentifier>>,
#[validate(nested)]
pub affiliation: Option<Vec<Affiliation>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Creators {
#[validate(nested)]
pub creator: Vec<Creator>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Date {
#[validate(custom(function = "is_rfc3339"))]
#[serde(alias = "$text")]
pub date: String,
#[serde(rename = "dateType", alias = "@dateType")]
pub date_type: DateType,
#[serde(rename = "dateInformation", alias = "@dateInformation")]
pub date_information: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Dates {
#[validate(nested)]
pub date: Vec<Date>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Description {
#[serde(alias = "$text")]
pub description: String,
#[serde(rename = "descriptionType", alias = "@descriptionType")]
pub description_type: Option<DescriptionType>,
#[validate(custom(function = "is_iso_639_1_language_code"))]
pub language: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Descriptions {
#[validate(nested)]
pub description: Vec<Description>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Formats {
pub format: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct FunderIdentifier {
#[serde(rename = "@funderIdentifierType")]
pub funder_identifier_type: Option<FunderIdentifierType>,
#[serde(rename = "$text")]
pub value: String,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct FundingReference {
#[serde(rename = "funderName")]
pub funder_name: String,
#[serde(rename = "funderIdentifier")]
pub funder_identifier: Option<String>,
#[serde(rename = "funderIdentifierType")]
pub funder_identifier_type: Option<FunderIdentifierType>,
#[validate(url)]
#[serde(rename = "schemeURI")]
pub scheme_uri: Option<String>,
#[serde(rename = "awardNumber")]
pub award_number: Option<String>,
#[validate(url)]
#[serde(rename = "awardURI")]
pub award_uri: Option<String>,
#[serde(rename = "awardTitle")]
pub award_title: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct FundingReferences {
#[validate(nested)]
#[serde(rename = "fundingReference")]
pub funding_reference: Vec<KernelFundingReference>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct GeoLocation {
#[validate(nested)]
#[serde(rename = "geoLocationPoint")]
pub geo_location_point: Option<GeoLocationPoint>,
#[validate(nested)]
#[serde(rename = "geoLocationBox")]
pub geo_location_box: Option<GeoLocationBox>,
#[serde(rename = "geoLocationPlace")]
pub geo_location_place: Option<String>,
#[validate(nested)]
#[serde(rename = "geoLocationPolygon")]
pub geo_location_polygon: Option<GeoLocationPolygon>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct GeoLocationBox {
#[validate(custom(function = "is_longitude"))]
#[serde(rename = "westBoundLongitude")]
pub west_bound_longitude: f64,
#[validate(custom(function = "is_longitude"))]
#[serde(rename = "eastBoundLongitude")]
pub east_bound_longitude: f64,
#[validate(custom(function = "is_latitude"))]
#[serde(rename = "southBoundLatitude")]
pub south_bound_latitude: f64,
#[validate(custom(function = "is_latitude"))]
#[serde(rename = "northBoundLatitude")]
pub north_bound_latitude: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct GeoLocationPoint {
#[validate(custom(function = "is_longitude"))]
#[serde(rename = "pointLongitude")]
pub longitude: f64,
#[validate(custom(function = "is_latitude"))]
#[serde(rename = "pointLatitude")]
pub latitude: f64,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct GeoLocationPolygon {
#[validate(nested, length(min = 4), custom(function = "is_polygon"))]
#[serde(rename = "polygonPoints", alias = "polygonPoint")]
pub polygon_points: Vec<GeoLocationPoint>,
#[validate(nested)]
#[serde(rename = "inPolygonPoint")]
pub in_polygon_point: Option<GeoLocationPoint>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct GeoLocations {
#[validate(nested)]
#[serde(rename = "geoLocation")]
pub geo_location: Vec<GeoLocation>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Identifier {
#[serde(default = "default_identifier_type", rename = "@identifierType")]
pub identifier_type: String,
#[validate(custom(function = "is_doi"))]
#[serde(rename = "$text")]
pub value: String,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct KernelFundingReference {
#[serde(rename = "funderName")]
pub funder_name: String,
#[serde(rename = "funderIdentifier")]
pub funder_identifier: Option<FunderIdentifier>,
#[validate(nested)]
#[serde(rename = "awardNumber")]
pub award_number: Option<AwardNumber>,
#[serde(rename = "awardTitle")]
pub award_title: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct NameIdentifier {
#[serde(rename = "nameIdentifier", alias = "$text")]
pub name_identifier: String,
#[serde(rename = "nameIdentifierScheme", alias = "@nameIdentifierScheme")]
pub name_identifier_scheme: Option<String>,
#[validate(url)]
#[serde(rename = "schemeUri", alias = "@schemeURI")]
pub scheme_uri: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Publisher {
pub name: String,
#[serde(rename = "publisherIdentifier")]
pub publisher_identifier: Option<String>,
#[serde(rename = "publisherIdentifierScheme")]
pub publisher_identifier_scheme: Option<String>,
#[validate(url)]
#[serde(rename = "schemeURI")]
pub scheme_uri: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Record {
#[validate(custom(function = "is_doi"))]
pub id: String,
#[serde(rename = "type")]
pub kind: String,
#[validate(nested)]
pub attributes: Attributes,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct RelatedIdentifier {
#[serde(rename = "relatedIdentifier", alias = "$text")]
pub related_identifier: String,
#[serde(rename = "relatedIdentifierType", alias = "@relatedIdentifierType")]
pub related_identifier_type: Option<RelatedIdentifierType>,
#[serde(rename = "relationType", alias = "@relationType")]
pub relation_type: Option<RelationType>,
#[serde(rename = "relatedMetadataScheme")]
pub related_metadata_scheme: Option<String>,
#[validate(url)]
#[serde(rename = "schemeURI")]
pub scheme_uri: Option<String>,
#[serde(rename = "schemeType")]
pub scheme_type: Option<String>,
#[serde(rename = "resourceTypeGeneral")]
pub resource_type_general: Option<ResourceTypeGeneral>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct RelatedIdentifiers {
#[validate(nested)]
#[serde(rename = "relatedIdentifier")]
pub related_identifier: Vec<RelatedIdentifier>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Resource {
#[validate(nested)]
pub identifier: Identifier,
#[validate(nested)]
pub creators: Creators,
#[validate(nested)]
pub titles: Titles,
pub publisher: String,
#[validate(custom(function = "is_year"))]
#[serde(rename = "publicationYear")]
pub publication_year: u32,
#[serde(rename = "resourceType")]
pub resource_type: ResourceTypes,
#[validate(nested)]
pub subjects: Option<Subjects>,
#[validate(nested)]
pub dates: Option<Dates>,
#[validate(custom(function = "is_iso_639_1_language_code"))]
pub language: Option<String>,
#[serde(rename = "relatedIdentifiers")]
pub related_identifiers: Option<RelatedIdentifiers>,
#[validate(nested)]
pub sizes: Option<Sizes>,
#[validate(nested)]
pub formats: Option<Formats>,
#[validate(custom(function = "is_semantic_version"))]
pub version: Option<String>,
#[validate(nested)]
#[serde(rename = "rightsList")]
pub rights_list: Option<RightsList>,
#[validate(nested)]
pub descriptions: Option<Descriptions>,
#[validate(nested)]
#[serde(rename = "geoLocations")]
pub geo_locations: Option<GeoLocations>,
#[validate(nested)]
#[serde(rename = "fundingReferences")]
pub funding_references: Option<FundingReferences>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct ResourceTypes {
#[serde(rename = "resourceTypeGeneral", alias = "@resourceTypeGeneral")]
pub resource_type_general: Option<ResourceTypeGeneral>,
#[serde(rename = "resourceType", alias = "$text")]
pub resource_type: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Rights {
#[serde(alias = "$text")]
pub rights: Option<String>,
#[validate(url)]
#[serde(rename = "rightsURI", alias = "@rightsURI")]
pub rights_uri: Option<String>,
#[validate(nested)]
#[serde(rename = "rightsIdentifier", alias = "@rightsIdentifier")]
pub rights_identifier: Option<License>,
#[serde(rename = "rightsIdentifierScheme", alias = "@rightsIdentifierScheme")]
pub rights_identifier_scheme: Option<String>,
#[validate(url)]
#[serde(rename = "schemeURI", alias = "@schemeURI")]
pub scheme_uri: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct RightsList {
#[validate(nested)]
pub rights: Vec<Rights>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Sizes {
pub size: Vec<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Subject {
#[serde(alias = "$text")]
pub subject: String,
#[validate(custom(function = "is_iso_639_1_language_code"))]
pub language: Option<String>,
#[serde(rename = "subjectScheme", alias = "@subjectScheme")]
pub subject_scheme: Option<String>,
#[validate(url)]
#[serde(rename = "schemeURI", alias = "@schemeURI")]
pub scheme_uri: Option<String>,
#[validate(url)]
#[serde(rename = "valueURI", alias = "@valueURI")]
pub value_uri: Option<String>,
#[serde(rename = "classificationCode", alias = "@classificationCode")]
pub classification_code: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Subjects {
#[validate(nested)]
pub subject: Vec<Subject>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Title {
#[serde(alias = "$text")]
pub title: String,
#[serde(rename = "titleType", alias = "@titleType")]
pub title_type: Option<TitleType>,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Validate)]
pub struct Titles {
pub title: Vec<Title>,
}
impl PartialEq for GeoLocationPoint {
fn eq(&self, other: &Self) -> bool {
self.longitude.to_bits() == other.longitude.to_bits() && self.latitude.to_bits() == other.latitude.to_bits()
}
}
impl Eq for GeoLocationPoint {}
impl fmt::Display for GeoLocationPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { longitude, latitude } = self;
write!(f, "({longitude}, {latitude})")
}
}
impl TryFrom<huwise::Dataset> for Record {
type Error = CrosswalkError;
fn try_from(dataset: huwise::Dataset) -> Result<Self, Self::Error> {
let mapping = huwise_to_datacite();
crosswalk::convert(&dataset, &mapping).map(|(record, _)| record)
}
}
impl TryFrom<dcat::Dataset> for Record {
type Error = CrosswalkError;
fn try_from(dataset: dcat::Dataset) -> Result<Self, Self::Error> {
let mapping = dcat_to_datacite();
crosswalk::convert(&dataset, &mapping).map(|(record, _)| record)
}
}
impl TryFrom<&dcat::Dataset> for Record {
type Error = CrosswalkError;
fn try_from(dataset: &dcat::Dataset) -> Result<Self, Self::Error> {
Record::try_from(dataset.clone())
}
}
impl TryFrom<invenio::Record> for Record {
type Error = CrosswalkError;
fn try_from(record: invenio::Record) -> Result<Self, Self::Error> {
let mapping = invenio_to_datacite();
crosswalk::convert(&record, &mapping).map(|(record, _)| record)
}
}
impl TryFrom<&invenio::Record> for Record {
type Error = CrosswalkError;
fn try_from(record: &invenio::Record) -> Result<Self, Self::Error> {
Record::try_from(record.clone())
}
}
impl SchemaBuilder for Record {
fn build_from_fields(fields: &Fields) -> Result<Self, CrosswalkError> {
let doi = fields.get_string("doi").unwrap_or_default();
let mut titles = None;
if let Some(title_str) = fields.get_string_opt("title") {
titles = Some(vec![Title {
title: title_str,
title_type: None,
}]);
}
let mut descriptions = None;
if let Some(desc_str) = fields.get_string_opt("description") {
descriptions = Some(vec![Description {
description: desc_str,
description_type: None,
language: None,
}]);
}
let language = fields.get_string_opt("language");
let mut creators = None;
if let Some(creator_names) = fields.get_string_vec_opt("creators") {
creators = Some(
creator_names
.into_iter()
.map(|name| Creator {
name,
name_type: None,
given_name: None,
family_name: None,
name_identifiers: None,
affiliation: None,
})
.collect(),
);
}
let mut publisher = None;
if let Some(pub_name) = fields.get_string_opt("publisher") {
publisher = Some(Publisher {
name: pub_name,
publisher_identifier: None,
publisher_identifier_scheme: None,
scheme_uri: None,
});
}
let publication_year = fields.get_number_opt("publication-year").map(|n| n as i32);
let mut attributes = Attributes {
doi,
event: None,
titles,
creators,
publisher,
publication_year,
resource_types: None,
url: fields.get_iri_opt("url"),
subjects: None,
contributors: None,
dates: None,
language,
alternate_identifiers: None,
related_identifiers: None,
sizes: None,
formats: None,
version: fields.get_string_opt("version"),
rights_list: None,
descriptions,
geo_locations: None,
funding_references: None,
schema_version: None,
};
if let Some(license_iri) = fields.get_iri_opt("license") {
attributes.rights_list = Some(vec![Rights {
rights: None,
rights_uri: Some(license_iri),
rights_identifier: None,
rights_identifier_scheme: None,
scheme_uri: None,
}]);
}
Ok(Record {
id: attributes.doi.clone(),
kind: "dois".to_string(),
attributes,
})
}
}
impl SchemaExtractor for Record {
fn extract_fields(&self) -> Fields {
let mut fields = Fields::new();
fields.insert("doi", FieldValue::String(self.attributes.doi.clone()));
if let Some(titles) = &self.attributes.titles {
if let Some(first) = titles.first() {
fields.insert("title", FieldValue::String(first.title.clone()));
if titles.len() > 1 {
let alt_titles: Vec<String> = titles.iter().skip(1).map(|t| t.title.clone()).collect();
fields.insert("alternative-titles", FieldValue::StringVec(alt_titles));
}
}
}
if let Some(descriptions) = &self.attributes.descriptions {
if let Some(first) = descriptions.first() {
fields.insert("description", FieldValue::String(first.description.clone()));
}
}
if let Some(language) = &self.attributes.language {
fields.insert("language", FieldValue::String(language.clone()));
}
if let Some(creators) = &self.attributes.creators {
if !creators.is_empty() {
let creator_names: Vec<String> = creators.iter().map(|c| c.name.clone()).collect();
fields.insert("creators", FieldValue::StringVec(creator_names));
}
}
if let Some(publisher) = &self.attributes.publisher {
fields.insert("publisher", FieldValue::String(publisher.name.clone()));
}
if let Some(pub_year) = self.attributes.publication_year {
fields.insert("publication-year", FieldValue::Number(pub_year as f64));
}
if let Some(url) = &self.attributes.url {
fields.insert("url", FieldValue::IRI(url.clone()));
}
if let Some(rights) = &self.attributes.rights_list {
if let Some(first) = rights.first() {
if let Some(uri) = &first.rights_uri {
fields.insert("license", FieldValue::IRI(uri.clone()));
}
}
}
if let Some(identifiers) = &self.attributes.alternate_identifiers {
if !identifiers.is_empty() {
let alt_ids: Vec<String> = identifiers.iter().map(|id| id.alternate_identifier.clone()).collect();
fields.insert("alternate-identifiers", FieldValue::StringVec(alt_ids));
}
}
if let Some(subjects) = &self.attributes.subjects {
if !subjects.is_empty() {
let subject_strings: Vec<String> = subjects.iter().map(|s| s.subject.clone()).collect();
fields.insert("subjects", FieldValue::StringVec(subject_strings));
}
}
if let Some(version) = &self.attributes.version {
fields.insert("version", FieldValue::String(version.clone()));
}
fields
}
}
impl ToProse for Record {
fn to_prose(&self) -> String {
self.attributes
.titles
.iter()
.flatten()
.map(|value| value.title.clone())
.chain(self.attributes.descriptions.iter().flatten().map(|value| value.description.clone()))
.chain(self.attributes.subjects.iter().flatten().map(|value| value.subject.clone()))
.collect::<Vec<String>>()
.join("\n\n")
}
}
#[cfg(feature = "std")]
impl InputOutput for Record {
fn read(path: impl Into<PathBuf>) -> ApiResult<Record> {
let source = path.into();
match MimeType::from(source.display().to_string()) {
| MimeType::Json => Record::read_json(source),
| MimeType::Yaml => Record::read_yaml(source),
| _ => Err(eyre!("Unsupported DataCite data file extension")),
}
}
fn read_json(path: PathBuf) -> ApiResult<Record> {
#[derive(Deserialize)]
#[serde(untagged)]
enum JsonInput {
One(Box<Record>),
Many(Vec<Record>),
}
read_file(path).and_then(|content| {
serde_json::from_str::<JsonInput>(&content)
.map_err(|why| eyre!("Failed to parse JSON DataCite record — {why}"))
.and_then(|value| match value {
| JsonInput::One(record) => Ok(*record),
| JsonInput::Many(records) => match records.len() {
| 1 => records
.into_iter()
.next()
.ok_or_else(|| eyre!("Expected one DataCite record but found none")),
| len => Err(eyre!("Expected one DataCite record but found {len}")),
},
})
})
}
fn read_yaml(path: PathBuf) -> ApiResult<Record> {
#[derive(Deserialize)]
#[serde(untagged)]
enum YamlInput {
One(Box<Record>),
Many(Vec<Record>),
}
read_file(path).and_then(|content| {
serde_norway::from_str::<YamlInput>(&content)
.map_err(|why| eyre!("Failed to parse YAML DataCite record — {why}"))
.and_then(|value| match value {
| YamlInput::One(record) => Ok(*record),
| YamlInput::Many(records) => match records.len() {
| 1 => records
.into_iter()
.next()
.ok_or_else(|| eyre!("Expected one DataCite record but found none")),
| len => Err(eyre!("Expected one DataCite record but found {len}")),
},
})
})
}
fn write(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
let output = path.into();
match MimeType::from(output.display().to_string()) {
| MimeType::Json => self.write_json(output),
| MimeType::Yaml => self.write_yaml(output),
| _ => Err(eyre!("Unsupported DataCite data file extension for writing")),
}
}
fn write_json(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
let output = path.into().with_extension("json");
serde_json::to_string_pretty(self)
.map_err(|why| eyre!("Failed to serialize JSON DataCite record — {why}"))
.and_then(|content| write_file(output, content))
}
fn write_yaml(&self, path: impl Into<PathBuf>) -> ApiResult<()> {
let output = path.into().with_extension("yaml");
serde_norway::to_string(self)
.map_err(|why| eyre!("Failed to serialize YAML DataCite record — {why}"))
.and_then(|content| write_file(output, content))
}
}
fn default_identifier_type() -> String {
DATACITE_IDENTIFIER_TYPE_CONTROLLED_VOCABULARY[0].to_string()
}