use crate::reference::types::{MultilingualString, Place};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(feature = "bindings")]
use specta::Type;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
#[serde(rename_all = "kebab-case")]
pub enum ContributorGender {
Masculine,
Feminine,
Neuter,
Common,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
#[serde(untagged)]
pub enum Contributor {
SimpleName(SimpleName),
StructuredName(StructuredName),
Multilingual(MultilingualName),
ContributorList(ContributorList),
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
#[serde(rename_all = "kebab-case")]
pub struct MultilingualName {
pub original: StructuredName,
#[serde(skip_serializing_if = "Option::is_none")]
pub lang: Option<crate::reference::types::LangID>,
#[serde(rename = "sort-as", skip_serializing_if = "Option::is_none")]
pub sort_as: Option<String>,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub transliterations: std::collections::HashMap<String, StructuredName>,
#[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
pub translations: std::collections::HashMap<crate::reference::types::LangID, StructuredName>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
pub struct SimpleName {
pub name: MultilingualString,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Place>,
#[serde(rename = "short-name", skip_serializing_if = "Option::is_none")]
pub short_name: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
#[serde(rename_all = "kebab-case")]
pub struct StructuredName {
pub given: MultilingualString,
pub family: MultilingualString,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dropping_particle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub non_dropping_particle: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
pub struct ContributorList(pub Vec<Contributor>);
impl Contributor {
pub fn to_names_vec(&self) -> Vec<FlatName> {
match self {
Contributor::SimpleName(n) => vec![FlatName {
literal: Some(n.name.to_string()),
short_name: n.short_name.clone(),
..Default::default()
}],
Contributor::StructuredName(n) => vec![FlatName {
given: Some(n.given.to_string()),
family: Some(n.family.to_string()),
suffix: n.suffix.clone(),
dropping_particle: n.dropping_particle.clone(),
non_dropping_particle: n.non_dropping_particle.clone(),
..Default::default()
}],
Contributor::Multilingual(m) => vec![FlatName {
given: Some(m.original.given.to_string()),
family: Some(m.original.family.to_string()),
suffix: m.original.suffix.clone(),
dropping_particle: m.original.dropping_particle.clone(),
non_dropping_particle: m.original.non_dropping_particle.clone(),
..Default::default()
}],
Contributor::ContributorList(l) => l.0.iter().flat_map(|c| c.to_names_vec()).collect(),
}
}
pub fn name(&self) -> Option<String> {
match self {
Contributor::SimpleName(n) => Some(n.name.to_string()),
Contributor::Multilingual(m) => {
Some(format!("{} {}", m.original.given, m.original.family))
}
_ => None,
}
}
pub fn location(&self) -> Option<String> {
match self {
Contributor::SimpleName(n) => n.location.clone().map(Into::into),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FlatName {
pub family: Option<String>,
pub given: Option<String>,
pub suffix: Option<String>,
pub dropping_particle: Option<String>,
pub non_dropping_particle: Option<String>,
pub literal: Option<String>,
pub short_name: Option<String>,
pub original_script: Option<String>,
}
impl FlatName {
pub fn family_or_literal(&self) -> &str {
if let Some(ref f) = self.family {
f
} else if let Some(ref l) = self.literal {
l
} else {
""
}
}
}
impl fmt::Display for Contributor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Contributor::SimpleName(n) => write!(f, "{}", n.name),
Contributor::StructuredName(n) => write!(f, "{} {}", n.given, n.family),
Contributor::Multilingual(m) => write!(f, "{} {}", m.original.given, m.original.family),
Contributor::ContributorList(l) => write!(f, "{}", l),
}
}
}
impl fmt::Display for ContributorList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names: Vec<String> = self.0.iter().map(|c| c.to_string()).collect();
write!(f, "{}", names.join(", "))
}
}
crate::tolerant_enum! {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContributorRole {
Author = "author",
Editor = "editor",
Translator = "translator",
Director = "director",
Performer = "performer",
Composer = "composer",
Illustrator = "illustrator",
Narrator = "narrator",
Host = "host",
Guest = "guest",
Interviewer = "interviewer",
Recipient = "recipient",
Compiler = "compiler",
Producer = "producer",
Writer = "writer",
Annotator = "annotator",
Commentator = "commentator",
ForewordAuthor = "foreword-author",
IntroductionAuthor = "introduction-author",
AfterwordAuthor = "afterword-author"
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
#[serde(transparent)]
pub struct ContributorRoles(
#[cfg_attr(feature = "schema", schemars(length(min = 1)))] Vec<ContributorRole>,
);
impl ContributorRoles {
#[must_use]
pub fn as_slice(&self) -> &[ContributorRole] {
&self.0
}
#[must_use]
pub fn contains(&self, role: &ContributorRole) -> bool {
self.0.contains(role)
}
#[cfg(feature = "legacy-convert")]
pub(crate) fn insert(&mut self, role: ContributorRole) {
if !self.contains(&role) {
self.0.push(role);
}
}
}
impl From<ContributorRole> for ContributorRoles {
fn from(role: ContributorRole) -> Self {
Self(vec![role])
}
}
impl PartialEq<ContributorRole> for ContributorRoles {
fn eq(&self, other: &ContributorRole) -> bool {
self.as_slice() == std::slice::from_ref(other)
}
}
impl TryFrom<Vec<ContributorRole>> for ContributorRoles {
type Error = &'static str;
fn try_from(roles: Vec<ContributorRole>) -> Result<Self, Self::Error> {
if roles.is_empty() {
return Err("contributor roles must not be empty");
}
let mut distinct = Vec::with_capacity(roles.len());
for role in &roles {
if distinct.contains(role) {
return Err("contributor roles must be distinct");
}
distinct.push(role.clone());
}
Ok(Self(roles))
}
}
impl<'de> Deserialize<'de> for ContributorRoles {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum AuthoredRoles {
Single(ContributorRole),
Multiple(Vec<ContributorRole>),
}
let roles = match AuthoredRoles::deserialize(deserializer)? {
AuthoredRoles::Single(role) => vec![role],
AuthoredRoles::Multiple(roles) => roles,
};
Self::try_from(roles).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "bindings", derive(Type))]
#[serde(rename_all = "kebab-case")]
pub struct ContributorEntry {
#[serde(rename = "roles", alias = "role")]
pub roles: ContributorRoles,
pub contributor: Contributor,
#[serde(skip_serializing_if = "Option::is_none")]
pub gender: Option<ContributorGender>,
}