Skip to main content

citum_engine/api/
types.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus and Citum contributors
4*/
5
6//! Request and response types for the interactive document API.
7
8use citum_schema::data::citation::{CitationLocator, IntegralNameState};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12pub use citum_schema_data::AbbreviationMap;
13
14/// Severity level for a structured diagnostic.
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(rename_all = "lowercase")]
17pub enum WarningLevel {
18    /// Non-fatal diagnostic message.
19    Warning,
20    /// Error that may affect output quality or correctness.
21    Error,
22}
23
24/// A structured diagnostic warning returned alongside formatted output.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Warning {
27    /// The severity level.
28    pub level: WarningLevel,
29    /// A machine-readable error code.
30    pub code: String,
31    /// The citation ID this warning pertains to, if any.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub citation_id: Option<String>,
34    /// The reference ID this warning pertains to, if any.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub ref_id: Option<String>,
37    /// A human-readable diagnostic message.
38    pub message: String,
39}
40
41/// Supported output format kinds.
42#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
43#[serde(rename_all = "lowercase")]
44pub enum OutputFormatKind {
45    /// Plain text (default).
46    #[default]
47    Plain,
48    /// HTML markup.
49    Html,
50    /// Djot inline markup.
51    Djot,
52    /// LaTeX markup.
53    Latex,
54    /// Typst markup.
55    Typst,
56    /// CommonMark/Markdown markup.
57    Markdown,
58}
59
60/// Controls how annotation text is rendered in an annotated bibliography.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct AnnotationStyle {
63    /// Markup format for annotation text. Default: Djot.
64    #[serde(default)]
65    pub format: AnnotationFormat,
66}
67
68impl Default for AnnotationStyle {
69    fn default() -> Self {
70        Self {
71            format: AnnotationFormat::Djot,
72        }
73    }
74}
75
76/// Markup format for annotation text.
77#[derive(Debug, Clone, Default, Serialize, Deserialize)]
78#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
79#[serde(rename_all = "snake_case")]
80pub enum AnnotationFormat {
81    /// Parse annotation as djot inline markup (default).
82    #[default]
83    Djot,
84    /// Treat annotation as plain text with no markup interpretation.
85    Plain,
86    /// Parse annotation as org-mode markup.
87    Org,
88}
89
90/// A single item within a citation occurrence.
91///
92/// Maps to `CitationItem` from `citum-schema-data`.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct CitationOccurrenceItem {
95    /// The reference ID (citekey) being cited.
96    pub id: String,
97    /// Optional locator (pinpoint citation).
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub locator: Option<CitationLocator>,
100    /// Optional prefix text before this item.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub prefix: Option<String>,
103    /// Optional suffix text after this item.
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub suffix: Option<String>,
106    /// Explicit integral (narrative) name state override.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub integral_name_state: Option<IntegralNameState>,
109    /// Explicit org-abbreviation state override.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub org_abbreviation_state: Option<IntegralNameState>,
112}
113
114impl From<CitationOccurrenceItem> for citum_schema::data::citation::CitationItem {
115    fn from(item: CitationOccurrenceItem) -> Self {
116        Self {
117            id: item.id,
118            locator: item.locator,
119            prefix: item.prefix,
120            suffix: item.suffix,
121            integral_name_state: item.integral_name_state,
122            org_abbreviation_state: item.org_abbreviation_state,
123        }
124    }
125}
126
127/// A citation occurrence in the document.
128///
129/// Maps to `Citation` from `citum-schema-data`.
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct CitationOccurrence {
132    /// Stable identifier for this citation in the document.
133    pub id: String,
134    /// The citation items (references cited together).
135    pub items: Vec<CitationOccurrenceItem>,
136    /// The citation mode (integral vs non-integral).
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub mode: Option<citum_schema::data::citation::CitationMode>,
139    /// Footnote/endnote number for note-based styles.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub note_number: Option<u32>,
142    /// Whether to suppress the author across all items.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub suppress_author: Option<bool>,
145    /// Whether this is a compound-numeric group.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub grouped: Option<bool>,
148    /// Optional prefix text before all formatted items.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub prefix: Option<String>,
151    /// Optional suffix text after all formatted items.
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub suffix: Option<String>,
154}
155
156impl From<CitationOccurrence> for citum_schema::data::citation::Citation {
157    fn from(occ: CitationOccurrence) -> Self {
158        Self {
159            id: Some(occ.id),
160            items: occ.items.into_iter().map(|item| item.into()).collect(),
161            mode: occ.mode.unwrap_or_default(),
162            note_number: occ.note_number,
163            suppress_author: occ.suppress_author.unwrap_or(false),
164            grouped: occ.grouped.unwrap_or(false),
165            prefix: occ.prefix,
166            suffix: occ.suffix,
167            position: None, // Assigned by processor
168        }
169    }
170}
171
172/// Document-level configuration for rendering.
173///
174/// Controls rendering behavior that belongs to the document rather than the style.
175#[derive(Debug, Clone, Default, Serialize, Deserialize)]
176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
177#[serde(default)]
178pub struct DocumentOptions {
179    /// Override or replace style-defined bibliography grouping.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub bibliography_groups: Option<Vec<citum_schema::grouping::BibliographyGroup>>,
182    /// Automatic bibliography partitioning by script or language.
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub sort_partitioning: Option<citum_schema::options::BibliographySortPartitioning>,
185    /// Document-level narrative citation rules.
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub integral_name_memory: Option<crate::processor::document::DocumentIntegralNameOverride>,
188    /// Whether to output semantic markup (HTML spans, Djot attributes).
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub show_semantics: Option<bool>,
191    /// Whether to annotate semantic wrappers with source template indices.
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub inject_ast_indices: Option<bool>,
194    /// Reference ID to annotation text mapping.
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub annotations: Option<HashMap<String, String>>,
197    /// Format for annotation text (djot, plain, org).
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub annotation_format: Option<AnnotationFormat>,
200    /// Map from full rendered strings to their abbreviations.
201    #[serde(alias = "abbreviation-map", skip_serializing_if = "Option::is_none")]
202    pub abbreviation_map: Option<AbbreviationMap>,
203}
204
205/// A single formatted citation.
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct FormattedCitation {
208    /// The citation identifier from the input.
209    pub id: String,
210    /// The formatted citation text.
211    pub text: String,
212    /// Referenced entry IDs from this citation.
213    pub ref_ids: Vec<String>,
214}
215
216/// Metadata extracted from a bibliography entry for interactivity.
217#[derive(Debug, Clone, Default, Serialize, Deserialize)]
218pub struct EntryMetadata {
219    /// Rendered author(s) string.
220    pub author: String,
221    /// Rendered year string.
222    pub year: String,
223    /// Rendered title string.
224    pub title: String,
225}
226
227/// A single bibliography entry.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct BibliographyEntry {
230    /// The reference ID.
231    pub id: String,
232    /// The formatted entry text.
233    pub text: String,
234    /// Extracted metadata for interactivity.
235    pub metadata: EntryMetadata,
236}
237
238/// A formatted bibliography.
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct FormattedBibliography {
241    /// The output format used.
242    pub format: OutputFormatKind,
243    /// The complete formatted bibliography content.
244    pub content: String,
245    /// Individual entries with metadata.
246    pub entries: Vec<BibliographyEntry>,
247}