Skip to main content

mant_protocol/
outline.rs

1//! Stable contracts for lightweight query outlines and selected excerpts.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use mant_ir::{
7    Block, DefinitionCase, DefinitionItem, DefinitionRole, Diagnostic, DocumentMeta,
8    DocumentSource, NodeId, Section, TldrDocument,
9};
10
11use crate::{NodePath, Producer};
12
13/// Exact schema marker for a query outline response.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15pub enum OutlineSchema {
16    /// Version 7 of the outline protocol.
17    #[serde(rename = "mant.outline/v7")]
18    V7,
19}
20
21/// Amount of semantic detail included in an outline projection.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
23#[serde(rename_all = "kebab-case")]
24pub enum OutlineDetail {
25    /// Include only section-level navigation nodes.
26    Sections,
27    /// Include sections and semantic definition entries.
28    Entries,
29}
30
31/// A block-free tree used to discover selectable query content.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
33#[serde(rename_all = "camelCase")]
34#[schemars(extend("$id" = "urn:mant:outline:v7"))]
35pub struct QueryOutline {
36    /// Exact response schema discriminator.
37    pub schema: OutlineSchema,
38    /// Detail level used to build this projection.
39    pub detail: OutlineDetail,
40    /// Human-readable selected-document label.
41    pub label: String,
42    /// Authoritative document source, when one was loaded.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub source: Option<DocumentSource>,
45    /// Document metadata, when an authoritative document was loaded.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub meta: Option<DocumentMeta>,
48    /// Recoverable parser findings available to diagnostic-oriented transports.
49    #[serde(default, skip_serializing_if = "Vec::is_empty")]
50    pub diagnostics: Vec<Diagnostic>,
51    /// False when semantic-entry declarations were rejected during lowering.
52    ///
53    /// The field is omitted for complete outlines so compact transports pay no
54    /// steady-state bandwidth cost.
55    #[serde(default = "default_true", skip_serializing_if = "is_true")]
56    pub entries_complete: bool,
57    /// Addressable nodes in document order.
58    pub nodes: Vec<OutlineNode>,
59}
60
61const fn default_true() -> bool {
62    true
63}
64
65// Serde's `skip_serializing_if` predicate receives a reference.
66#[allow(clippy::trivially_copy_pass_by_ref)]
67const fn is_true(value: &bool) -> bool {
68    *value
69}
70
71/// One uniquely addressable node in a query outline.
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73#[serde(
74    tag = "kind",
75    rename_all = "kebab-case",
76    rename_all_fields = "camelCase"
77)]
78pub enum OutlineNode {
79    /// Optional quick-reference node.
80    Tldr {
81        /// Canonical structural outline path.
82        path: NodePath,
83        /// Stable document-local identity.
84        id: NodeId,
85        /// Display title.
86        title: String,
87    },
88    /// Addressable document content that precedes the first heading.
89    DocumentRoot {
90        /// Canonical structural outline path.
91        path: NodePath,
92        /// Virtual document-root identity.
93        id: NodeId,
94        /// Display title for the leading content.
95        title: String,
96    },
97    /// One semantic document section.
98    DocumentSection {
99        /// Canonical structural outline path.
100        path: NodePath,
101        /// Stable document-local section identity.
102        id: NodeId,
103        /// Section heading text.
104        title: String,
105        /// Nested section and entry nodes.
106        children: Vec<OutlineNode>,
107    },
108    /// One semantic command, option, or variable definition.
109    DocumentEntry {
110        /// Canonical structural outline path.
111        path: NodePath,
112        /// Stable document-local entry identity.
113        id: NodeId,
114        /// Primary display term.
115        title: String,
116        /// Semantic category of the entry.
117        role: DefinitionRole,
118        /// Alias case-matching policy.
119        case: DefinitionCase,
120        /// Normalized selectable aliases.
121        names: Vec<String>,
122    },
123}
124
125impl OutlineNode {
126    /// Return the canonical structural path.
127    #[must_use]
128    pub fn path(&self) -> &str {
129        match self {
130            Self::Tldr { path, .. }
131            | Self::DocumentRoot { path, .. }
132            | Self::DocumentSection { path, .. }
133            | Self::DocumentEntry { path, .. } => path,
134        }
135    }
136
137    /// Return the stable document-local identity.
138    #[must_use]
139    pub fn id(&self) -> &str {
140        match self {
141            Self::Tldr { id, .. }
142            | Self::DocumentRoot { id, .. }
143            | Self::DocumentSection { id, .. }
144            | Self::DocumentEntry { id, .. } => id,
145        }
146    }
147
148    /// Return the node's display title.
149    #[must_use]
150    pub fn title(&self) -> &str {
151        match self {
152            Self::Tldr { title, .. }
153            | Self::DocumentRoot { title, .. }
154            | Self::DocumentSection { title, .. }
155            | Self::DocumentEntry { title, .. } => title,
156        }
157    }
158
159    /// Return child nodes, or an empty slice for leaf variants.
160    #[must_use]
161    pub fn children(&self) -> &[Self] {
162        match self {
163            Self::DocumentSection { children, .. } => children,
164            Self::Tldr { .. } | Self::DocumentRoot { .. } | Self::DocumentEntry { .. } => &[],
165        }
166    }
167}
168
169/// Exact schema marker for selected query content.
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
171pub enum ExcerptSchema {
172    /// Version 7 of the excerpt protocol.
173    #[serde(rename = "mant.excerpt/v7")]
174    V7,
175}
176
177/// One or more independently selected nodes from a complete query.
178#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
179#[serde(rename_all = "camelCase")]
180#[schemars(extend("$id" = "urn:mant:excerpt:v7"))]
181pub struct QueryExcerpt {
182    /// Exact response schema discriminator.
183    pub schema: ExcerptSchema,
184    /// Human-readable selected-document label.
185    pub label: String,
186    /// Process and parser provenance, when a document was loaded.
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub producer: Option<Producer>,
189    /// Authoritative document source, when one was loaded.
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub source: Option<DocumentSource>,
192    /// Document metadata, when one was loaded.
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub meta: Option<DocumentMeta>,
195    /// Recoverable parser and validation findings.
196    #[serde(default, skip_serializing_if = "Vec::is_empty")]
197    pub diagnostics: Vec<Diagnostic>,
198    /// Selected nodes in request order.
199    pub selections: Vec<ExcerptSelection>,
200}
201
202/// One selected document node together with its location in the complete outline.
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
204#[serde(
205    tag = "kind",
206    rename_all = "kebab-case",
207    rename_all_fields = "camelCase"
208)]
209pub enum ExcerptSelection {
210    /// Optional quick-reference content preceding the primary document.
211    Tldr {
212        /// Canonical structural outline path.
213        path: NodePath,
214        /// Stable document-local identity.
215        id: NodeId,
216        /// Display title.
217        title: String,
218        /// Complete quick-reference content.
219        document: TldrDocument,
220    },
221    /// Complete document content that appears before the first heading.
222    DocumentRoot {
223        /// Canonical structural outline path.
224        path: NodePath,
225        /// Virtual document-root identity.
226        id: NodeId,
227        /// Display title for the leading content.
228        title: String,
229        /// Complete leading blocks.
230        blocks: Vec<Block>,
231    },
232    /// Complete selected document node, including all descendant sections.
233    DocumentSection {
234        /// Canonical structural outline path.
235        path: NodePath,
236        /// Stable document-local section identity.
237        id: NodeId,
238        /// Section heading text.
239        title: String,
240        /// Ordered ancestors from the document root to the parent section.
241        #[serde(default, skip_serializing_if = "Vec::is_empty")]
242        breadcrumbs: Vec<OutlineReference>,
243        /// Complete selected section including descendants.
244        section: Section,
245    },
246    /// One addressable semantic definition and its complete description.
247    DocumentEntry {
248        /// Canonical structural outline path.
249        path: NodePath,
250        /// Stable document-local entry identity.
251        id: NodeId,
252        /// Primary display term.
253        title: String,
254        /// Ordered containing sections from outermost to innermost.
255        #[serde(default, skip_serializing_if = "Vec::is_empty")]
256        breadcrumbs: Vec<OutlineReference>,
257        /// Complete semantic definition.
258        entry: DefinitionItem,
259    },
260}
261
262/// Compact ancestor identity attached to an excerpt selection.
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
264#[serde(rename_all = "camelCase")]
265pub struct OutlineReference {
266    /// Canonical structural outline path.
267    pub path: NodePath,
268    /// Stable document-local identity.
269    pub id: NodeId,
270    /// Display title.
271    pub title: String,
272}