Skip to main content

mant_protocol/
document.rs

1//! Versioned wire representation of normalized document IR.
2
3use mant_ir::{
4    Block, Diagnostic, Document as IrDocument, DocumentMeta, DocumentSource, ParserInfo, Section,
5};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9/// Exact schema marker for a normalized structured document response.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
11pub enum DocumentSchema {
12    /// Version 0.11 of the pre-stable structured-document protocol.
13    #[serde(rename = "mant.document/v0.11")]
14    V0Dot11,
15}
16
17impl DocumentSchema {
18    /// Serialized identifier of the current document contract.
19    pub const ID: &'static str = "mant.document/v0.11";
20}
21
22/// Identifies `ManT` and the parser used to build a wire document.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24#[serde(rename_all = "camelCase")]
25pub struct Producer {
26    /// Process implementation name.
27    pub name: String,
28    /// Process package version.
29    pub version: String,
30    /// Parser implementation, when an authoritative document was parsed.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub engine: Option<Engine>,
33}
34
35/// Parser implementation recorded at the process boundary.
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
37#[serde(rename_all = "camelCase")]
38pub struct Engine {
39    /// Parser implementation name.
40    pub name: String,
41    /// Parser implementation version.
42    pub version: String,
43}
44
45/// Serializable v0.11 envelope around `ManT`'s protocol-independent document IR.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
47#[serde(rename_all = "camelCase", deny_unknown_fields)]
48pub struct DocumentResponse {
49    /// Exact response schema discriminator.
50    pub schema: DocumentSchema,
51    /// Process and parser provenance.
52    pub producer: Producer,
53    /// Original source identity.
54    pub source: DocumentSource,
55    /// Source-neutral document metadata.
56    pub meta: DocumentMeta,
57    /// Original visible heading; independent from bibliographic metadata.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub heading: Option<mant_ir::Heading>,
60    /// Exact source fragments resolving to the normalized document root.
61    #[serde(default, skip_serializing_if = "Vec::is_empty")]
62    pub fragment_aliases: Vec<mant_ir::FragmentAlias>,
63    /// Recoverable parsing and validation findings.
64    #[serde(default, skip_serializing_if = "Vec::is_empty")]
65    pub diagnostics: Vec<Diagnostic>,
66    /// Content preceding the first section.
67    #[serde(default, skip_serializing_if = "Vec::is_empty")]
68    pub blocks: Vec<Block>,
69    /// Top-level semantic sections in source order.
70    pub sections: Vec<Section>,
71}
72
73impl Producer {
74    /// Construct process provenance for a normalized document.
75    #[must_use]
76    pub fn for_document(document: &IrDocument) -> Self {
77        Self {
78            name: "mant".to_owned(),
79            version: env!("CARGO_PKG_VERSION").to_owned(),
80            engine: document.parser.as_ref().map(|parser| Engine {
81                name: parser.name.clone(),
82                version: parser.version.clone(),
83            }),
84        }
85    }
86}
87
88impl From<&IrDocument> for DocumentResponse {
89    fn from(document: &IrDocument) -> Self {
90        Self {
91            schema: DocumentSchema::V0Dot11,
92            producer: Producer::for_document(document),
93            source: document.source.clone(),
94            meta: document.meta.clone(),
95            heading: document.heading.clone(),
96            fragment_aliases: document.fragment_aliases.clone(),
97            diagnostics: document.diagnostics.clone(),
98            blocks: document.blocks.clone(),
99            sections: document.sections.clone(),
100        }
101    }
102}
103
104impl From<DocumentResponse> for IrDocument {
105    fn from(document: DocumentResponse) -> Self {
106        Self {
107            parser: document.producer.engine.map(|engine| ParserInfo {
108                name: engine.name,
109                version: engine.version,
110            }),
111            source: document.source,
112            meta: document.meta,
113            heading: document.heading,
114            fragment_aliases: document.fragment_aliases,
115            diagnostics: document.diagnostics,
116            blocks: document.blocks,
117            sections: document.sections,
118        }
119    }
120}