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}