1use schemars::JsonSchema;
4use serde::{Deserialize, Deserializer, Serialize};
5
6use mant_ir::{
7 Block, DefinitionCase, DefinitionItem, DefinitionRole, Diagnostic, DocumentMeta,
8 DocumentSource, EntryKind, EntrySummary, NodeId, Section, TldrDocument, ValueDomain,
9};
10
11use crate::{NodePath, NodeSelector, Producer};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15pub enum OutlineSchema {
16 #[serde(rename = "mant.outline/v0.10")]
18 V0Dot10,
19}
20
21impl OutlineSchema {
22 pub const ID: &'static str = "mant.outline/v0.10";
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, JsonSchema)]
28#[serde(
29 tag = "kind",
30 rename_all = "kebab-case",
31 rename_all_fields = "camelCase",
32 deny_unknown_fields
33)]
34pub enum EntryProjection {
35 None,
37 #[default]
39 Summary,
40 All,
42 Kinds {
44 #[schemars(length(min = 1, max = 9))]
46 kinds: Vec<EntryKind>,
47 },
48}
49
50#[derive(Deserialize)]
51#[serde(
52 tag = "kind",
53 rename_all = "kebab-case",
54 rename_all_fields = "camelCase",
55 deny_unknown_fields
56)]
57enum ClosedEntryProjection {
58 None {},
59 Summary {},
60 All {},
61 Kinds { kinds: Vec<EntryKind> },
62}
63
64impl<'de> Deserialize<'de> for EntryProjection {
65 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66 where
67 D: Deserializer<'de>,
68 {
69 Ok(match ClosedEntryProjection::deserialize(deserializer)? {
70 ClosedEntryProjection::None {} => Self::None,
71 ClosedEntryProjection::Summary {} => Self::Summary,
72 ClosedEntryProjection::All {} => Self::All,
73 ClosedEntryProjection::Kinds { kinds } => Self::Kinds { kinds },
74 })
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum OutlineDetail {
81 Sections,
83 Entries,
85}
86
87impl From<OutlineDetail> for EntryProjection {
88 fn from(value: OutlineDetail) -> Self {
89 match value {
90 OutlineDetail::Sections => Self::None,
91 OutlineDetail::Entries => Self::All,
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
98#[serde(rename_all = "camelCase")]
99#[schemars(extend("$id" = "urn:mant:outline:v0.10"))]
100pub struct QueryOutline {
101 pub schema: OutlineSchema,
103 pub entries: EntryProjection,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub root: Option<NodeSelector>,
108 pub label: String,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub source: Option<DocumentSource>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub meta: Option<DocumentMeta>,
116 #[serde(default, skip_serializing_if = "Vec::is_empty")]
118 pub diagnostics: Vec<Diagnostic>,
119 #[serde(default = "default_true", skip_serializing_if = "is_true")]
124 pub entries_complete: bool,
125 pub nodes: Vec<OutlineNode>,
127}
128
129const fn default_true() -> bool {
130 true
131}
132
133#[allow(clippy::trivially_copy_pass_by_ref)]
135const fn is_true(value: &bool) -> bool {
136 *value
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
141#[serde(
142 tag = "kind",
143 rename_all = "kebab-case",
144 rename_all_fields = "camelCase"
145)]
146pub enum OutlineNode {
147 Tldr {
149 path: NodePath,
151 id: NodeId,
153 title: String,
155 },
156 DocumentRoot {
158 path: NodePath,
160 id: NodeId,
162 title: String,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 entry_summary: Option<EntrySummary>,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
169 children: Vec<OutlineNode>,
170 },
171 DocumentSection {
173 path: NodePath,
175 id: NodeId,
177 title: String,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 entry_summary: Option<EntrySummary>,
182 children: Vec<OutlineNode>,
184 },
185 DocumentEntry {
187 path: NodePath,
189 id: NodeId,
191 title: String,
193 entry_kind: EntryKind,
195 case: DefinitionCase,
197 aliases: Vec<String>,
199 forms: Vec<String>,
201 targets: Vec<NodeId>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 value_domain: Option<ValueDomain>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 entry_summary: Option<EntrySummary>,
209 #[serde(default, skip_serializing_if = "Vec::is_empty")]
211 children: Vec<OutlineNode>,
212 },
213}
214
215impl OutlineNode {
216 #[must_use]
218 pub fn path(&self) -> &str {
219 match self {
220 Self::Tldr { path, .. }
221 | Self::DocumentRoot { path, .. }
222 | Self::DocumentSection { path, .. }
223 | Self::DocumentEntry { path, .. } => path,
224 }
225 }
226
227 #[must_use]
229 pub fn id(&self) -> &str {
230 match self {
231 Self::Tldr { id, .. }
232 | Self::DocumentRoot { id, .. }
233 | Self::DocumentSection { id, .. }
234 | Self::DocumentEntry { id, .. } => id,
235 }
236 }
237
238 #[must_use]
240 pub fn title(&self) -> &str {
241 match self {
242 Self::Tldr { title, .. }
243 | Self::DocumentRoot { title, .. }
244 | Self::DocumentSection { title, .. }
245 | Self::DocumentEntry { title, .. } => title,
246 }
247 }
248
249 #[must_use]
251 pub fn children(&self) -> &[Self] {
252 match self {
253 Self::DocumentRoot { children, .. }
254 | Self::DocumentSection { children, .. }
255 | Self::DocumentEntry { children, .. } => children,
256 Self::Tldr { .. } => &[],
257 }
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
263pub enum ExcerptSchema {
264 #[serde(rename = "mant.excerpt/v0.10")]
266 V0Dot10,
267}
268
269impl ExcerptSchema {
270 pub const ID: &'static str = "mant.excerpt/v0.10";
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
276#[serde(rename_all = "camelCase")]
277#[schemars(extend("$id" = "urn:mant:excerpt:v0.10"))]
278pub struct QueryExcerpt {
279 pub schema: ExcerptSchema,
281 pub label: String,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub producer: Option<Producer>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub source: Option<DocumentSource>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub meta: Option<DocumentMeta>,
292 #[serde(default, skip_serializing_if = "Vec::is_empty")]
294 pub diagnostics: Vec<Diagnostic>,
295 pub selections: Vec<ExcerptSelection>,
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
301#[serde(
302 tag = "kind",
303 rename_all = "kebab-case",
304 rename_all_fields = "camelCase"
305)]
306pub enum ExcerptSelection {
307 Tldr {
309 outline: OutlineTrail,
311 document: TldrDocument,
313 },
314 DocumentRoot {
316 outline: OutlineTrail,
318 blocks: Vec<Block>,
320 },
321 DocumentSection {
323 outline: OutlineTrail,
325 section: Section,
327 },
328 DocumentEntry {
330 outline: OutlineTrail,
332 entry: DefinitionItem,
334 },
335}
336
337impl ExcerptSelection {
338 #[must_use]
340 pub const fn outline(&self) -> &OutlineTrail {
341 match self {
342 Self::Tldr { outline, .. }
343 | Self::DocumentRoot { outline, .. }
344 | Self::DocumentSection { outline, .. }
345 | Self::DocumentEntry { outline, .. } => outline,
346 }
347 }
348}
349
350#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
352#[serde(rename_all = "camelCase")]
353pub struct OutlineTrail {
354 #[serde(default, skip_serializing_if = "Vec::is_empty")]
356 pub ancestors: Vec<OutlineReference>,
357 pub node: OutlineNodeReference,
359}
360
361impl OutlineTrail {
362 #[must_use]
364 pub fn path(&self) -> &str {
365 self.node.path()
366 }
367
368 #[must_use]
370 pub fn title(&self) -> &str {
371 self.node.title()
372 }
373}
374
375#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
377#[serde(rename_all = "camelCase")]
378pub struct OutlineReference {
379 pub path: NodePath,
381 pub id: NodeId,
383 pub title: String,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
389#[serde(
390 tag = "kind",
391 rename_all = "kebab-case",
392 rename_all_fields = "camelCase"
393)]
394pub enum OutlineNodeReference {
395 Tldr {
397 path: NodePath,
399 id: NodeId,
401 title: String,
403 },
404 DocumentRoot {
406 path: NodePath,
408 id: NodeId,
410 title: String,
412 },
413 DocumentSection {
415 path: NodePath,
417 id: NodeId,
419 title: String,
421 },
422 DocumentEntry {
424 path: NodePath,
426 id: NodeId,
428 title: String,
430 role: DefinitionRole,
432 case: DefinitionCase,
434 names: Vec<String>,
436 },
437}
438
439impl OutlineNodeReference {
440 #[must_use]
442 pub fn path(&self) -> &str {
443 match self {
444 Self::Tldr { path, .. }
445 | Self::DocumentRoot { path, .. }
446 | Self::DocumentSection { path, .. }
447 | Self::DocumentEntry { path, .. } => path,
448 }
449 }
450
451 #[must_use]
453 pub fn id(&self) -> &str {
454 match self {
455 Self::Tldr { id, .. }
456 | Self::DocumentRoot { id, .. }
457 | Self::DocumentSection { id, .. }
458 | Self::DocumentEntry { id, .. } => id,
459 }
460 }
461
462 #[must_use]
464 pub fn title(&self) -> &str {
465 match self {
466 Self::Tldr { title, .. }
467 | Self::DocumentRoot { title, .. }
468 | Self::DocumentSection { title, .. }
469 | Self::DocumentEntry { title, .. } => title,
470 }
471 }
472}