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 7 of the structured-document protocol.
13    #[serde(rename = "mant.document/v7")]
14    V7,
15}
16
17/// Identifies `ManT` and the parser used to build a wire document.
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
19#[serde(rename_all = "camelCase")]
20pub struct Producer {
21    /// Process implementation name.
22    pub name: String,
23    /// Process package version.
24    pub version: String,
25    /// Parser implementation, when an authoritative document was parsed.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub engine: Option<Engine>,
28}
29
30/// Parser implementation recorded at the process boundary.
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
32#[serde(rename_all = "camelCase")]
33pub struct Engine {
34    /// Parser implementation name.
35    pub name: String,
36    /// Parser implementation version.
37    pub version: String,
38}
39
40/// Serializable v7 envelope around `ManT`'s protocol-independent document IR.
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42#[serde(rename_all = "camelCase")]
43pub struct DocumentResponse {
44    /// Exact response schema discriminator.
45    pub schema: DocumentSchema,
46    /// Process and parser provenance.
47    pub producer: Producer,
48    /// Original source identity.
49    pub source: DocumentSource,
50    /// Source-neutral document metadata.
51    pub meta: DocumentMeta,
52    /// Recoverable parsing and validation findings.
53    #[serde(default, skip_serializing_if = "Vec::is_empty")]
54    pub diagnostics: Vec<Diagnostic>,
55    /// Content preceding the first section.
56    #[serde(default, skip_serializing_if = "Vec::is_empty")]
57    pub blocks: Vec<Block>,
58    /// Top-level semantic sections in source order.
59    pub sections: Vec<Section>,
60}
61
62impl Producer {
63    /// Construct process provenance for a normalized document.
64    #[must_use]
65    pub fn for_document(document: &IrDocument) -> Self {
66        Self {
67            name: "mant".to_owned(),
68            version: env!("CARGO_PKG_VERSION").to_owned(),
69            engine: document.parser.as_ref().map(|parser| Engine {
70                name: parser.name.clone(),
71                version: parser.version.clone(),
72            }),
73        }
74    }
75}
76
77impl From<&IrDocument> for DocumentResponse {
78    fn from(document: &IrDocument) -> Self {
79        Self {
80            schema: DocumentSchema::V7,
81            producer: Producer::for_document(document),
82            source: document.source.clone(),
83            meta: document.meta.clone(),
84            diagnostics: document.diagnostics.clone(),
85            blocks: document.blocks.clone(),
86            sections: document.sections.clone(),
87        }
88    }
89}
90
91impl From<DocumentResponse> for IrDocument {
92    fn from(document: DocumentResponse) -> Self {
93        Self {
94            parser: document.producer.engine.map(|engine| ParserInfo {
95                name: engine.name,
96                version: engine.version,
97            }),
98            source: document.source,
99            meta: document.meta,
100            diagnostics: document.diagnostics,
101            blocks: document.blocks,
102            sections: document.sections,
103        }
104    }
105}