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 0.9 of the pre-stable outline protocol.
17    #[serde(rename = "mant.outline/v0.9")]
18    V0Dot9,
19}
20
21impl OutlineSchema {
22    /// Serialized identifier of the current outline contract.
23    pub const ID: &'static str = "mant.outline/v0.9";
24}
25
26/// Amount of semantic detail included in an outline projection.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
28#[serde(rename_all = "kebab-case")]
29pub enum OutlineDetail {
30    /// Include only section-level navigation nodes.
31    Sections,
32    /// Include sections and semantic definition entries.
33    Entries,
34}
35
36/// A block-free tree used to discover selectable query content.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
38#[serde(rename_all = "camelCase")]
39#[schemars(extend("$id" = "urn:mant:outline:v0.9"))]
40pub struct QueryOutline {
41    /// Exact response schema discriminator.
42    pub schema: OutlineSchema,
43    /// Detail level used to build this projection.
44    pub detail: OutlineDetail,
45    /// Human-readable selected-document label.
46    pub label: String,
47    /// Authoritative document source, when one was loaded.
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub source: Option<DocumentSource>,
50    /// Document metadata, when an authoritative document was loaded.
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub meta: Option<DocumentMeta>,
53    /// Recoverable parser findings available to diagnostic-oriented transports.
54    #[serde(default, skip_serializing_if = "Vec::is_empty")]
55    pub diagnostics: Vec<Diagnostic>,
56    /// False when semantic-entry declarations were rejected during lowering.
57    ///
58    /// The field is omitted for complete outlines so compact transports pay no
59    /// steady-state bandwidth cost.
60    #[serde(default = "default_true", skip_serializing_if = "is_true")]
61    pub entries_complete: bool,
62    /// Addressable nodes in document order.
63    pub nodes: Vec<OutlineNode>,
64}
65
66const fn default_true() -> bool {
67    true
68}
69
70// Serde's `skip_serializing_if` predicate receives a reference.
71#[allow(clippy::trivially_copy_pass_by_ref)]
72const fn is_true(value: &bool) -> bool {
73    *value
74}
75
76/// One uniquely addressable node in a query outline.
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78#[serde(
79    tag = "kind",
80    rename_all = "kebab-case",
81    rename_all_fields = "camelCase"
82)]
83pub enum OutlineNode {
84    /// Optional quick-reference node.
85    Tldr {
86        /// Canonical structural outline path.
87        path: NodePath,
88        /// Stable document-local identity.
89        id: NodeId,
90        /// Display title.
91        title: String,
92    },
93    /// Addressable document content that precedes the first heading.
94    DocumentRoot {
95        /// Canonical structural outline path.
96        path: NodePath,
97        /// Virtual document-root identity.
98        id: NodeId,
99        /// Display title for the leading content.
100        title: String,
101    },
102    /// One semantic document section.
103    DocumentSection {
104        /// Canonical structural outline path.
105        path: NodePath,
106        /// Stable document-local section identity.
107        id: NodeId,
108        /// Section heading text.
109        title: String,
110        /// Nested section and entry nodes.
111        children: Vec<OutlineNode>,
112    },
113    /// One semantic command, option, or variable definition.
114    DocumentEntry {
115        /// Canonical structural outline path.
116        path: NodePath,
117        /// Stable document-local entry identity.
118        id: NodeId,
119        /// Primary display term.
120        title: String,
121        /// Semantic category of the entry.
122        role: DefinitionRole,
123        /// Alias case-matching policy.
124        case: DefinitionCase,
125        /// Normalized selectable aliases.
126        names: Vec<String>,
127    },
128}
129
130impl OutlineNode {
131    /// Return the canonical structural path.
132    #[must_use]
133    pub fn path(&self) -> &str {
134        match self {
135            Self::Tldr { path, .. }
136            | Self::DocumentRoot { path, .. }
137            | Self::DocumentSection { path, .. }
138            | Self::DocumentEntry { path, .. } => path,
139        }
140    }
141
142    /// Return the stable document-local identity.
143    #[must_use]
144    pub fn id(&self) -> &str {
145        match self {
146            Self::Tldr { id, .. }
147            | Self::DocumentRoot { id, .. }
148            | Self::DocumentSection { id, .. }
149            | Self::DocumentEntry { id, .. } => id,
150        }
151    }
152
153    /// Return the node's display title.
154    #[must_use]
155    pub fn title(&self) -> &str {
156        match self {
157            Self::Tldr { title, .. }
158            | Self::DocumentRoot { title, .. }
159            | Self::DocumentSection { title, .. }
160            | Self::DocumentEntry { title, .. } => title,
161        }
162    }
163
164    /// Return child nodes, or an empty slice for leaf variants.
165    #[must_use]
166    pub fn children(&self) -> &[Self] {
167        match self {
168            Self::DocumentSection { children, .. } => children,
169            Self::Tldr { .. } | Self::DocumentRoot { .. } | Self::DocumentEntry { .. } => &[],
170        }
171    }
172}
173
174/// Exact schema marker for selected query content.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
176pub enum ExcerptSchema {
177    /// Version 0.9 of the pre-stable excerpt protocol.
178    #[serde(rename = "mant.excerpt/v0.9")]
179    V0Dot9,
180}
181
182impl ExcerptSchema {
183    /// Serialized identifier of the current excerpt contract.
184    pub const ID: &'static str = "mant.excerpt/v0.9";
185}
186
187/// One or more independently selected nodes from a complete query.
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
189#[serde(rename_all = "camelCase")]
190#[schemars(extend("$id" = "urn:mant:excerpt:v0.9"))]
191pub struct QueryExcerpt {
192    /// Exact response schema discriminator.
193    pub schema: ExcerptSchema,
194    /// Human-readable selected-document label.
195    pub label: String,
196    /// Process and parser provenance, when a document was loaded.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub producer: Option<Producer>,
199    /// Authoritative document source, when one was loaded.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub source: Option<DocumentSource>,
202    /// Document metadata, when one was loaded.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub meta: Option<DocumentMeta>,
205    /// Recoverable parser and validation findings.
206    #[serde(default, skip_serializing_if = "Vec::is_empty")]
207    pub diagnostics: Vec<Diagnostic>,
208    /// Selected nodes in request order.
209    pub selections: Vec<ExcerptSelection>,
210}
211
212/// One selected document node together with its location in the complete outline.
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
214#[serde(
215    tag = "kind",
216    rename_all = "kebab-case",
217    rename_all_fields = "camelCase"
218)]
219pub enum ExcerptSelection {
220    /// Optional quick-reference content preceding the primary document.
221    Tldr {
222        /// Complete logical location in the document outline.
223        outline: OutlineTrail,
224        /// Complete quick-reference content.
225        document: TldrDocument,
226    },
227    /// Complete document content that appears before the first heading.
228    DocumentRoot {
229        /// Complete logical location in the document outline.
230        outline: OutlineTrail,
231        /// Complete leading blocks.
232        blocks: Vec<Block>,
233    },
234    /// Complete selected document node, including all descendant sections.
235    DocumentSection {
236        /// Complete logical location in the document outline.
237        outline: OutlineTrail,
238        /// Complete selected section including descendants.
239        section: Section,
240    },
241    /// One addressable semantic definition and its complete description.
242    DocumentEntry {
243        /// Complete logical location in the document outline.
244        outline: OutlineTrail,
245        /// Complete semantic definition.
246        entry: DefinitionItem,
247    },
248}
249
250impl ExcerptSelection {
251    /// Return the complete logical location of this selection.
252    #[must_use]
253    pub const fn outline(&self) -> &OutlineTrail {
254        match self {
255            Self::Tldr { outline, .. }
256            | Self::DocumentRoot { outline, .. }
257            | Self::DocumentSection { outline, .. }
258            | Self::DocumentEntry { outline, .. } => outline,
259        }
260    }
261}
262
263/// Complete logical location of one addressable document node.
264#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
265#[serde(rename_all = "camelCase")]
266pub struct OutlineTrail {
267    /// Ordered ancestors from the document root to the direct parent.
268    #[serde(default, skip_serializing_if = "Vec::is_empty")]
269    pub ancestors: Vec<OutlineReference>,
270    /// Selected or matching node at the end of the trail.
271    pub node: OutlineNodeReference,
272}
273
274impl OutlineTrail {
275    /// Return the canonical structural path of the terminal node.
276    #[must_use]
277    pub fn path(&self) -> &str {
278        self.node.path()
279    }
280
281    /// Return the display title of the terminal node.
282    #[must_use]
283    pub fn title(&self) -> &str {
284        self.node.title()
285    }
286}
287
288/// Compact ancestor identity attached to an excerpt selection.
289#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
290#[serde(rename_all = "camelCase")]
291pub struct OutlineReference {
292    /// Canonical structural outline path.
293    pub path: NodePath,
294    /// Stable document-local identity.
295    pub id: NodeId,
296    /// Display title.
297    pub title: String,
298}
299
300/// Compact typed identity for the terminal node in an [`OutlineTrail`].
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
302#[serde(
303    tag = "kind",
304    rename_all = "kebab-case",
305    rename_all_fields = "camelCase"
306)]
307pub enum OutlineNodeReference {
308    /// Optional quick-reference node.
309    Tldr {
310        /// Canonical structural outline path.
311        path: NodePath,
312        /// Stable document-local identity.
313        id: NodeId,
314        /// Display title.
315        title: String,
316    },
317    /// Addressable content before the first heading.
318    DocumentRoot {
319        /// Canonical structural outline path.
320        path: NodePath,
321        /// Virtual document-root identity.
322        id: NodeId,
323        /// Display title.
324        title: String,
325    },
326    /// One semantic document section.
327    DocumentSection {
328        /// Canonical structural outline path.
329        path: NodePath,
330        /// Stable document-local identity.
331        id: NodeId,
332        /// Section heading text.
333        title: String,
334    },
335    /// One semantic command, option, or variable definition.
336    DocumentEntry {
337        /// Canonical structural outline path.
338        path: NodePath,
339        /// Stable document-local identity.
340        id: NodeId,
341        /// Primary display term.
342        title: String,
343        /// Semantic category of the definition.
344        role: DefinitionRole,
345        /// Alias case-matching policy.
346        case: DefinitionCase,
347        /// Normalized selectable aliases.
348        names: Vec<String>,
349    },
350}
351
352impl OutlineNodeReference {
353    /// Return the canonical structural path.
354    #[must_use]
355    pub fn path(&self) -> &str {
356        match self {
357            Self::Tldr { path, .. }
358            | Self::DocumentRoot { path, .. }
359            | Self::DocumentSection { path, .. }
360            | Self::DocumentEntry { path, .. } => path,
361        }
362    }
363
364    /// Return the stable document-local identity.
365    #[must_use]
366    pub fn id(&self) -> &str {
367        match self {
368            Self::Tldr { id, .. }
369            | Self::DocumentRoot { id, .. }
370            | Self::DocumentSection { id, .. }
371            | Self::DocumentEntry { id, .. } => id,
372        }
373    }
374
375    /// Return the display title.
376    #[must_use]
377    pub fn title(&self) -> &str {
378        match self {
379            Self::Tldr { title, .. }
380            | Self::DocumentRoot { title, .. }
381            | Self::DocumentSection { title, .. }
382            | Self::DocumentEntry { title, .. } => title,
383        }
384    }
385}