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#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
44#[serde(rename_all = "lowercase")]
45pub enum OutputFormatKind {
46    /// Plain text (default).
47    #[default]
48    Plain,
49    /// HTML markup.
50    Html,
51    /// Djot inline markup.
52    Djot,
53    /// LaTeX markup.
54    Latex,
55    /// Typst markup.
56    Typst,
57    /// CommonMark/Markdown markup.
58    Markdown,
59}
60
61/// Controls how annotation text is rendered in an annotated bibliography.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct AnnotationStyle {
64    /// Markup format for annotation text. Default: Djot.
65    #[serde(default)]
66    pub format: AnnotationFormat,
67}
68
69impl Default for AnnotationStyle {
70    fn default() -> Self {
71        Self {
72            format: AnnotationFormat::Djot,
73        }
74    }
75}
76
77/// Markup format for annotation text.
78#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
80#[serde(rename_all = "snake_case")]
81pub enum AnnotationFormat {
82    /// Parse annotation as djot inline markup (default).
83    #[default]
84    Djot,
85    /// Treat annotation as plain text with no markup interpretation.
86    Plain,
87    /// Parse annotation as org-mode markup.
88    Org,
89}
90
91/// A single item within a citation occurrence.
92///
93/// Maps to `CitationItem` from `citum-schema-data`.
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
96pub struct CitationOccurrenceItem {
97    /// The reference ID (citekey) being cited.
98    pub id: String,
99    /// Optional locator (pinpoint citation).
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub locator: Option<CitationLocator>,
102    /// Optional prefix text before this item.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub prefix: Option<String>,
105    /// Optional suffix text after this item.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub suffix: Option<String>,
108    /// Explicit integral (narrative) name state override.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub integral_name_state: Option<IntegralNameState>,
111    /// Explicit org-abbreviation state override.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub org_abbreviation_state: Option<IntegralNameState>,
114}
115
116impl From<CitationOccurrenceItem> for citum_schema::data::citation::CitationItem {
117    fn from(item: CitationOccurrenceItem) -> Self {
118        Self {
119            id: item.id,
120            locator: item.locator,
121            prefix: item.prefix,
122            suffix: item.suffix,
123            integral_name_state: item.integral_name_state,
124            org_abbreviation_state: item.org_abbreviation_state,
125        }
126    }
127}
128
129/// A citation occurrence in the document.
130///
131/// Maps to `Citation` from `citum-schema-data`.
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
133#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
134pub struct CitationOccurrence {
135    /// Stable identifier for this citation in the document.
136    pub id: String,
137    /// The citation items (references cited together).
138    pub items: Vec<CitationOccurrenceItem>,
139    /// The citation mode (integral vs non-integral).
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub mode: Option<citum_schema::data::citation::CitationMode>,
142    /// Footnote/endnote number for note-based styles.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub note_number: Option<u32>,
145    /// Whether to suppress the author across all items.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub suppress_author: Option<bool>,
148    /// Whether this is a compound-numeric group.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub grouped: Option<bool>,
151    /// Optional prefix text before all formatted items.
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub prefix: Option<String>,
154    /// Optional suffix text after all formatted items.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub suffix: Option<String>,
157    /// Signal that this cluster opens a sentence; host supplies this explicitly.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub sentence_start: Option<bool>,
160}
161
162impl From<CitationOccurrence> for citum_schema::data::citation::Citation {
163    fn from(occ: CitationOccurrence) -> Self {
164        Self {
165            id: Some(occ.id),
166            items: occ.items.into_iter().map(|item| item.into()).collect(),
167            mode: occ.mode.unwrap_or_default(),
168            note_number: occ.note_number,
169            suppress_author: occ.suppress_author.unwrap_or(false),
170            grouped: occ.grouped.unwrap_or(false),
171            sentence_start: occ.sentence_start.unwrap_or(false),
172            prefix: occ.prefix,
173            suffix: occ.suffix,
174            position: None, // Assigned by processor
175        }
176    }
177}
178
179/// Document-level configuration for rendering.
180///
181/// Controls rendering behavior that belongs to the document rather than the style.
182#[derive(Debug, Clone, Default, Serialize, Deserialize)]
183#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
184#[serde(default)]
185pub struct DocumentOptions {
186    /// Override or replace style-defined bibliography grouping.
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub bibliography_groups: Option<Vec<citum_schema::grouping::BibliographyGroup>>,
189    /// Automatic bibliography partitioning by script or language.
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub sort_partitioning: Option<citum_schema::options::BibliographySortPartitioning>,
192    /// Document-level narrative citation rules.
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub integral_name_memory: Option<crate::processor::document::DocumentIntegralNameOverride>,
195    /// Whether to output semantic markup (HTML spans, Djot attributes).
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub show_semantics: Option<bool>,
198    /// Whether to annotate semantic wrappers with source template indices.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub inject_ast_indices: Option<bool>,
201    /// Reference ID to annotation text mapping.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub annotations: Option<HashMap<String, String>>,
204    /// Format for annotation text (djot, plain, org).
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub annotation_format: Option<AnnotationFormat>,
207    /// Map from full rendered strings to their abbreviations.
208    #[serde(alias = "abbreviation-map", skip_serializing_if = "Option::is_none")]
209    pub abbreviation_map: Option<AbbreviationMap>,
210}
211
212/// A single formatted citation.
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct FormattedCitation {
215    /// The citation identifier from the input.
216    pub id: String,
217    /// The formatted citation text.
218    pub text: String,
219    /// Referenced entry IDs from this citation.
220    pub ref_ids: Vec<String>,
221}
222
223/// Metadata extracted from a bibliography entry for interactivity.
224#[derive(Debug, Clone, Default, Serialize, Deserialize)]
225#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
226pub struct EntryMetadata {
227    /// Rendered author(s) string.
228    pub author: String,
229    /// Rendered year string.
230    pub year: String,
231    /// Rendered title string.
232    pub title: String,
233}
234
235/// A single bibliography entry.
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
238pub struct BibliographyEntry {
239    /// The reference ID.
240    pub id: String,
241    /// The formatted entry text.
242    pub text: String,
243    /// Extracted metadata for interactivity.
244    pub metadata: EntryMetadata,
245}
246
247/// A formatted bibliography.
248#[derive(Debug, Clone, Serialize, Deserialize)]
249#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
250pub struct FormattedBibliography {
251    /// The output format used.
252    pub format: OutputFormatKind,
253    /// The complete formatted bibliography content.
254    pub content: String,
255    /// Individual entries with metadata.
256    pub entries: Vec<BibliographyEntry>,
257}
258
259/// A requested bibliography block in document order.
260#[derive(Debug, Clone, Serialize, Deserialize)]
261#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
262pub struct BibliographyBlockRequest {
263    /// Stable identifier for this bibliography block.
264    pub id: String,
265    /// Group selector, heading, sorting, and optional template override.
266    pub group: citum_schema::grouping::BibliographyGroup,
267}
268
269/// A formatted bibliography block.
270#[derive(Debug, Clone, Serialize, Deserialize)]
271#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
272pub struct FormattedBibliographyBlock {
273    /// The bibliography block identifier from the request.
274    pub id: String,
275    /// The resolved heading text, if the block has one.
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub heading: Option<String>,
278    /// The rendered block body without a document-level heading wrapper.
279    pub content: String,
280    /// Individual entries rendered in this block.
281    pub entries: Vec<BibliographyEntry>,
282}