use crate::validation::{validate_license, IntegerOrString, MonthValue, NumberOrString, PostalCode, Validate, ValidationReport, YearValue};
use crate::OneOrMany;
use acorn_core::prelude::alloc::{String, ToString, Vec};
use acorn_core::util::{MarkdownSupport, ToProse};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
pub type Catalog = Vec<Cff>;
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
#[serde(untagged)]
pub enum Agent {
Entity(Entity),
Person(Person),
}
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum CffType {
Dataset,
Software,
}
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum IdentifierType {
Doi,
Other,
Swh,
Url,
}
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum PublicationStatus {
Abstract,
AdvanceOnline,
InPreparation,
InPress,
Preprint,
Submitted,
}
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ReferenceType {
Art,
Article,
Audiovisual,
Bill,
Blog,
Book,
Catalogue,
Conference,
ConferencePaper,
Data,
Database,
Dictionary,
EditedWork,
Encyclopedia,
FilmBroadcast,
Generic,
GovernmentDocument,
Grant,
Hearing,
HistoricalWork,
LegalCase,
LegalRule,
MagazineArticle,
Manual,
Map,
Multimedia,
Music,
NewspaperArticle,
Pamphlet,
Patent,
PersonalCommunication,
Proceedings,
Report,
Serial,
Slides,
Software,
SoftwareCode,
SoftwareContainer,
SoftwareExecutable,
SoftwareVirtualMachine,
SoundRecording,
Standard,
Statute,
Thesis,
Unpublished,
Video,
Website,
}
#[skip_serializing_none]
#[derive(Clone, Debug, eserde::Deserialize, JsonSchema, PartialEq, Serialize, Validate)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Cff {
#[serde(rename = "abstract")]
pub abstract_text: Option<String>,
#[validate(nested)]
#[eserde(compat)]
pub authors: Vec<Agent>,
pub cff_version: String,
pub commit: Option<String>,
#[validate(nested)]
#[eserde(compat)]
pub contact: Option<Vec<Agent>>,
#[validate(date)]
pub date_released: Option<String>,
#[validate(doi)]
pub doi: Option<String>,
#[validate(nested)]
#[eserde(compat)]
pub identifiers: Option<Vec<Identifier>>,
pub keywords: Option<Vec<String>>,
#[validate(custom(function = "validate_license"))]
#[eserde(compat)]
pub license: Option<OneOrMany<String>>,
#[validate(url)]
pub license_url: Option<String>,
pub message: String,
#[validate(nested)]
#[eserde(compat)]
pub preferred_citation: Option<Reference>,
#[validate(nested)]
#[eserde(compat)]
pub references: Option<Vec<Reference>>,
#[validate(url)]
pub repository: Option<String>,
#[validate(url)]
pub repository_artifact: Option<String>,
#[validate(url)]
pub repository_code: Option<String>,
pub title: String,
#[serde(rename = "type")]
#[eserde(compat)]
pub kind: Option<CffType>,
#[validate(url)]
pub url: Option<String>,
#[validate(version)]
pub version: Option<String>,
}
impl Cff {
pub fn embedded(content: &str) -> Vec<Self> {
content
.split("```")
.enumerate()
.filter(|(index, _)| index % 2 == 1)
.filter_map(|(_, block)| {
let value = block
.strip_prefix("cff\n")
.or_else(|| block.strip_prefix("yaml\n"))
.or_else(|| block.strip_prefix("yml\n"))
.unwrap_or(block);
value
.contains("cff-version")
.then(|| serde_norway::from_str::<Self>(value).ok())
.flatten()
})
.collect()
}
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize, Validate)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Entity {
pub address: Option<String>,
pub alias: Option<String>,
pub city: Option<String>,
#[validate(country)]
pub country: Option<String>,
#[validate(date)]
pub date_end: Option<String>,
#[validate(date)]
pub date_start: Option<String>,
#[validate(email)]
pub email: Option<String>,
pub fax: Option<String>,
pub location: Option<String>,
pub name: String,
#[validate(orcid)]
pub orcid: Option<String>,
#[validate(nested)]
pub postal_code: Option<PostalCode>,
pub region: Option<String>,
#[validate(phone)]
pub tel: Option<String>,
#[validate(url)]
pub website: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize, Validate)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Identifier {
pub description: Option<String>,
#[serde(rename = "type")]
pub kind: IdentifierType,
pub value: String,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize, Validate)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Person {
pub address: Option<String>,
pub affiliation: Option<String>,
pub alias: Option<String>,
pub city: Option<String>,
#[validate(country)]
pub country: Option<String>,
#[validate(email)]
pub email: Option<String>,
pub family_names: Option<String>,
pub fax: Option<String>,
pub given_names: Option<String>,
pub name_particle: Option<String>,
pub name_suffix: Option<String>,
#[validate(orcid)]
pub orcid: Option<String>,
#[validate(nested)]
pub postal_code: Option<PostalCode>,
pub region: Option<String>,
#[validate(phone)]
pub tel: Option<String>,
#[validate(url)]
pub website: Option<String>,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize, Validate)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Reference {
pub abbreviation: Option<String>,
#[serde(rename = "abstract")]
pub abstract_text: Option<String>,
#[validate(nested)]
pub authors: Vec<Agent>,
#[validate(doi)]
pub collection_doi: Option<String>,
pub collection_title: Option<String>,
pub collection_type: Option<String>,
#[validate(commit)]
pub commit: Option<String>,
#[validate(nested)]
pub conference: Option<Entity>,
#[validate(nested)]
pub contact: Option<Vec<Agent>>,
pub copyright: Option<String>,
pub data_type: Option<String>,
pub database: Option<String>,
pub database_provider: Option<Entity>,
#[validate(date)]
pub date_accessed: Option<String>,
#[validate(date)]
pub date_downloaded: Option<String>,
#[validate(date)]
pub date_published: Option<String>,
#[validate(date)]
pub date_released: Option<String>,
pub department: Option<String>,
#[validate(doi)]
pub doi: Option<String>,
pub edition: Option<String>,
#[validate(nested)]
pub editors: Option<Vec<Agent>>,
#[validate(nested)]
pub editors_series: Option<Vec<Agent>>,
#[validate(nested)]
pub end: Option<IntegerOrString>,
pub entry: Option<String>,
pub filename: Option<String>,
pub format: Option<String>,
#[validate(nested)]
pub identifiers: Option<Vec<Identifier>>,
#[validate(nested)]
pub institution: Option<Entity>,
#[validate(isbn)]
pub isbn: Option<String>,
pub issn: Option<String>,
pub issue: Option<NumberOrString>,
#[validate(date)]
pub issue_date: Option<String>,
pub issue_title: Option<String>,
pub journal: Option<String>,
pub keywords: Option<Vec<String>>,
pub languages: Option<Vec<String>>,
#[validate(custom(function = "validate_license"))]
pub license: Option<OneOrMany<String>>,
#[validate(url)]
pub license_url: Option<String>,
pub loc_end: Option<IntegerOrString>,
pub loc_start: Option<IntegerOrString>,
#[validate(nested)]
pub location: Option<Entity>,
pub medium: Option<String>,
#[validate(nested)]
pub month: Option<MonthValue>,
pub nihmsid: Option<String>,
pub notes: Option<String>,
pub number: Option<NumberOrString>,
pub number_volumes: Option<IntegerOrString>,
pub pages: Option<IntegerOrString>,
#[validate(each(state))]
pub patent_states: Option<Vec<String>>,
pub pmcid: Option<String>,
#[validate(nested)]
pub publisher: Option<Entity>,
#[validate(nested)]
pub recipients: Option<Vec<Agent>>,
#[validate(url)]
pub repository: Option<String>,
#[validate(url)]
pub repository_artifact: Option<String>,
#[validate(url)]
pub repository_code: Option<String>,
pub scope: Option<String>,
#[validate(nested)]
pub section: Option<NumberOrString>,
#[validate(nested)]
pub senders: Option<Vec<Agent>>,
pub start: Option<IntegerOrString>,
pub status: Option<PublicationStatus>,
pub term: Option<String>,
pub thesis_type: Option<String>,
pub title: String,
#[validate(nested)]
pub translators: Option<Vec<Agent>>,
#[serde(rename = "type")]
pub kind: ReferenceType,
#[validate(url)]
pub url: Option<String>,
#[validate(version)]
pub version: Option<String>,
pub volume: Option<IntegerOrString>,
pub volume_title: Option<String>,
#[validate(nested)]
pub year: Option<YearValue>,
#[validate(nested)]
pub year_original: Option<YearValue>,
}
impl Default for Cff {
fn default() -> Self {
Self {
abstract_text: None,
authors: Vec::new(),
cff_version: "1.2.0".to_string(),
commit: None,
contact: None,
date_released: None,
doi: None,
identifiers: None,
keywords: None,
license: None,
license_url: None,
message: "If you use this software, please cite it using the metadata provided in this file.".to_string(),
preferred_citation: None,
references: None,
repository: None,
repository_artifact: None,
repository_code: None,
title: String::new(),
kind: None,
url: None,
version: None,
}
}
}
impl MarkdownSupport for Cff {
fn to_markdown(&self) -> String {
serde_norway::to_string(self).unwrap_or_default()
}
}
impl ToProse for Cff {
fn to_prose(&self) -> String {
[Some(self.title.to_string()), self.abstract_text.clone(), Some(self.message.clone())]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join("\n\n")
}
}
impl Validate for Agent {
fn validate(&self) -> Result<(), ValidationReport> {
match self {
| Self::Entity(value) => value.validate(),
| Self::Person(value) => value.validate(),
}
}
}