Skip to main content

citum_schema_style/style/
metadata.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus
4*/
5
6//! Style metadata and classification types.
7
8#[cfg(feature = "schema")]
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12#[allow(unused_imports, reason = "Referenced by intra-doc links.")]
13use crate::ResolutionError;
14
15/// Discipline/field classification for a citation style.
16///
17/// Values correspond to the CSL 1.0 `<category field="..."/>` attribute,
18/// `generic-base` is silently ignored during migration.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[cfg_attr(feature = "schema", derive(JsonSchema))]
21#[serde(rename_all = "kebab-case")]
22pub enum CitationField {
23    /// Anthropology styles.
24    Anthropology,
25    /// Biology styles.
26    Biology,
27    /// Botany styles.
28    Botany,
29    /// Chemistry styles.
30    Chemistry,
31    /// Communications studies styles.
32    Communications,
33    /// Engineering styles.
34    Engineering,
35    /// Geography styles.
36    Geography,
37    /// Geology styles.
38    Geology,
39    /// History styles.
40    History,
41    /// Humanities styles.
42    Humanities,
43    /// Law styles.
44    Law,
45    /// Linguistics styles.
46    Linguistics,
47    /// Literature styles.
48    Literature,
49    /// Mathematics styles.
50    Math,
51    /// Medicine styles.
52    Medicine,
53    /// Philosophy styles.
54    Philosophy,
55    /// Physics styles.
56    Physics,
57    #[serde(rename = "political-science")]
58    /// Political science styles.
59    PoliticalScience,
60    /// Psychology styles.
61    Psychology,
62    /// General science styles.
63    Science,
64    #[serde(rename = "social-science")]
65    /// Social science styles.
66    SocialScience,
67    /// Sociology styles.
68    Sociology,
69    /// Theology styles.
70    Theology,
71    /// Zoology styles.
72    Zoology,
73}
74
75/// A hyperlink associated with a style (documentation, self-link, etc.).
76#[derive(Debug, Default, Clone, Deserialize, Serialize)]
77#[cfg_attr(feature = "schema", derive(JsonSchema))]
78#[serde(rename_all = "kebab-case")]
79pub struct StyleLink {
80    /// Link target for related style metadata.
81    pub href: String,
82    /// Relationship type for the link, such as `self` or `documentation`.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub rel: Option<String>,
85}
86
87/// A person credit (author or contributor) for a style.
88#[derive(Debug, Default, Clone, Deserialize, Serialize)]
89#[cfg_attr(feature = "schema", derive(JsonSchema))]
90#[serde(rename_all = "kebab-case")]
91pub struct StylePerson {
92    /// Display name for the credited person.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub name: Option<String>,
95    /// Contact email for the credited person.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub email: Option<String>,
98    /// URI identifying the credited person.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub uri: Option<String>,
101}
102
103/// Provenance block for styles adapted from a CSL 1.0 source.
104#[derive(Debug, Default, Clone, Deserialize, Serialize)]
105#[cfg_attr(feature = "schema", derive(JsonSchema))]
106#[serde(rename_all = "kebab-case")]
107pub struct StyleSource {
108    /// The original CSL style ID (URI).
109    pub csl_id: String,
110    /// Who performed the adaptation (e.g., "citum-migrate" or a person's name).
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub adapted_by: Option<String>,
113    /// License URI (e.g., CC BY-SA).
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub license: Option<String>,
116    /// Original CSL style authors.
117    #[serde(skip_serializing_if = "Vec::is_empty", default)]
118    pub original_authors: Vec<StylePerson>,
119    /// Links from the original CSL style (documentation, self, etc.).
120    #[serde(skip_serializing_if = "Vec::is_empty", default)]
121    pub links: Vec<StyleLink>,
122}
123
124/// Style metadata.
125#[derive(Debug, Default, Deserialize, Serialize, Clone)]
126#[cfg_attr(feature = "schema", derive(JsonSchema))]
127#[serde(rename_all = "kebab-case")]
128pub struct StyleInfo {
129    /// Human-readable title of the style.
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub title: Option<String>,
132    /// Stable identifier for the style, usually a URI or slug.
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub id: Option<String>,
135    /// Short summary of the style's intended use or provenance.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub description: Option<String>,
138    /// Default locale for the style (e.g., "en-US", "de-DE").
139    /// Used for locale-aware term resolution.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub default_locale: Option<String>,
142    /// Discipline classifications for this style.
143    #[serde(skip_serializing_if = "Vec::is_empty", default)]
144    pub fields: Vec<CitationField>,
145    /// Provenance: set when this style was adapted from a CSL 1.0 source.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub source: Option<StyleSource>,
148    /// Concise display name for the style family, used by UIs to label
149    /// search results and match banners (e.g. `"APA"`, `"Chicago Notes"`,
150    /// `"MLA"`). Omit for journal-specific styles whose full title is their
151    /// identity. Combine with `edition` to produce labels like `"APA 7th"`.
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub short_name: Option<String>,
154    /// Edition or version qualifier used alongside `short_name` to
155    /// disambiguate multiple editions of the same style family
156    /// (e.g. `"7th"`, `"18th edition"`). Omit when only one edition exists.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub edition: Option<String>,
159    /// Minimum Citum engine version required to load and render this style.
160    ///
161    /// Accepts a [`semver::VersionReq`]-compatible string (e.g. `">=0.38.0"`,
162    /// `"^0.40"`). When the running engine does not satisfy the requirement,
163    /// the resolver returns
164    /// [`ResolutionError::VersionMismatch`]
165    /// instead of attempting to deserialize fields the engine may not
166    /// understand. Omit for styles that use only stable, long-lived features.
167    #[serde(rename = "citum-version", skip_serializing_if = "Option::is_none")]
168    pub citum_version: Option<String>,
169}
170
171impl StyleInfo {
172    /// Returns `true` when all fields are absent (no content to merge).
173    pub fn is_empty(&self) -> bool {
174        self.title.is_none()
175            && self.id.is_none()
176            && self.description.is_none()
177            && self.default_locale.is_none()
178            && self.fields.is_empty()
179            && self.source.is_none()
180            && self.short_name.is_none()
181            && self.edition.is_none()
182            && self.citum_version.is_none()
183    }
184}