1use schemars::JsonSchema;
4use serde::{Deserialize, Deserializer, Serialize};
5
6use mant_ir::{
7 Block, Diagnostic, DocumentAddress, DocumentMeta, DocumentReference, DocumentSource, EntryKind,
8 EntrySummary, NameCase, NodeId, Section, TldrDocument,
9};
10
11use crate::{ContentSelector, NodePath, Producer};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15pub enum OutlineSchema {
16 #[serde(rename = "mant.outline/v0.11")]
18 V0Dot11,
19}
20
21impl OutlineSchema {
22 pub const ID: &'static str = "mant.outline/v0.11";
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", deny_unknown_fields)]
99#[schemars(extend("$id" = "urn:mant:outline:v0.11"))]
100pub struct QueryOutline {
101 pub schema: OutlineSchema,
103 pub entries: EntryProjection,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub root: Option<ContentSelector>,
108 pub references: crate::ReferenceInventory,
110 pub label: String,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub display_title: Option<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub address: Option<DocumentAddress>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub source: Option<DocumentSource>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub meta: Option<DocumentMeta>,
124 #[serde(default, skip_serializing_if = "Vec::is_empty")]
126 pub diagnostics: Vec<Diagnostic>,
127 #[serde(default = "default_true", skip_serializing_if = "is_true")]
132 pub semantics_complete: bool,
133 pub nodes: Vec<OutlineNode>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
139#[serde(rename_all = "camelCase", deny_unknown_fields)]
140pub struct EntryDocumentTarget {
141 #[schemars(length(min = 1))]
143 pub label: String,
144 pub reference: DocumentReference,
146 #[serde(skip_serializing_if = "Option::is_none")]
151 pub address: Option<DocumentAddress>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
156#[serde(
157 tag = "kind",
158 rename_all = "kebab-case",
159 rename_all_fields = "camelCase",
160 deny_unknown_fields
161)]
162pub enum EntryValueDomain {
163 Choices {
165 exhaustive: bool,
167 },
168 EntrySet {
170 reference: DocumentReference,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 address: Option<DocumentAddress>,
175 #[schemars(length(min = 1, max = 9))]
177 entry_kinds: Vec<EntryKind>,
178 },
179}
180
181const fn default_true() -> bool {
182 true
183}
184
185#[allow(clippy::trivially_copy_pass_by_ref)]
187const fn is_true(value: &bool) -> bool {
188 *value
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
193#[serde(
194 tag = "kind",
195 rename_all = "kebab-case",
196 rename_all_fields = "camelCase",
197 deny_unknown_fields
198)]
199pub enum OutlineNode {
200 Tldr {
202 path: NodePath,
204 id: NodeId,
206 title: String,
208 },
209 DocumentRoot {
211 path: NodePath,
213 id: NodeId,
215 title: String,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 entry_summary: Option<EntrySummary>,
220 #[serde(default, skip_serializing_if = "Vec::is_empty")]
222 children: Vec<OutlineNode>,
223 },
224 DocumentSection {
226 path: NodePath,
228 id: NodeId,
230 title: String,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 entry_summary: Option<EntrySummary>,
235 children: Vec<OutlineNode>,
237 },
238 DocumentEntry {
240 owner: Box<mant_ir::ContentReveal>,
243 path: NodePath,
245 id: NodeId,
247 title: String,
249 entry_kind: EntryKind,
251 case: NameCase,
253 names: Vec<String>,
255 #[serde(default, skip_serializing_if = "Vec::is_empty")]
257 alias_groups: Vec<Vec<String>>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
260 alias_of: Option<NodeId>,
261 forms: Vec<String>,
263 #[serde(default, skip_serializing_if = "Vec::is_empty")]
265 document_targets: Vec<EntryDocumentTarget>,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 value_domain: Option<Box<EntryValueDomain>>,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 entry_summary: Option<EntrySummary>,
272 #[serde(default, skip_serializing_if = "Vec::is_empty")]
274 children: Vec<OutlineNode>,
275 },
276}
277
278impl OutlineNode {
279 #[must_use]
281 pub fn path(&self) -> &str {
282 match self {
283 Self::Tldr { path, .. }
284 | Self::DocumentRoot { path, .. }
285 | Self::DocumentSection { path, .. }
286 | Self::DocumentEntry { path, .. } => path,
287 }
288 }
289
290 #[must_use]
292 pub fn id(&self) -> &str {
293 match self {
294 Self::Tldr { id, .. }
295 | Self::DocumentRoot { id, .. }
296 | Self::DocumentSection { id, .. }
297 | Self::DocumentEntry { id, .. } => id,
298 }
299 }
300
301 #[must_use]
303 pub fn title(&self) -> &str {
304 match self {
305 Self::Tldr { title, .. }
306 | Self::DocumentRoot { title, .. }
307 | Self::DocumentSection { title, .. }
308 | Self::DocumentEntry { title, .. } => title,
309 }
310 }
311
312 #[must_use]
314 pub fn children(&self) -> &[Self] {
315 match self {
316 Self::DocumentRoot { children, .. }
317 | Self::DocumentSection { children, .. }
318 | Self::DocumentEntry { children, .. } => children,
319 Self::Tldr { .. } => &[],
320 }
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
326pub enum ExcerptSchema {
327 #[serde(rename = "mant.excerpt/v0.11")]
329 V0Dot11,
330}
331
332impl ExcerptSchema {
333 pub const ID: &'static str = "mant.excerpt/v0.11";
335}
336
337#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
339#[serde(rename_all = "camelCase", deny_unknown_fields)]
340#[schemars(extend("$id" = "urn:mant:excerpt:v0.11"))]
341pub struct QueryExcerpt {
342 pub schema: ExcerptSchema,
344 pub label: String,
346 #[serde(default, skip_serializing_if = "Option::is_none")]
348 pub display_title: Option<String>,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub address: Option<DocumentAddress>,
352 #[serde(default = "default_true", skip_serializing_if = "is_true")]
355 pub semantics_complete: bool,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub producer: Option<Producer>,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub source: Option<DocumentSource>,
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub meta: Option<DocumentMeta>,
365 #[serde(default, skip_serializing_if = "Vec::is_empty")]
367 pub diagnostics: Vec<Diagnostic>,
368 pub selections: Vec<ExcerptSelection>,
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
374#[serde(
375 tag = "kind",
376 rename_all = "kebab-case",
377 rename_all_fields = "camelCase",
378 deny_unknown_fields
379)]
380pub enum ExcerptSelection {
381 Tldr {
383 outline: OutlineTrail,
385 document: TldrDocument,
387 },
388 DocumentRoot {
390 outline: OutlineTrail,
392 #[serde(default, skip_serializing_if = "Option::is_none")]
394 heading: Option<mant_ir::Heading>,
395 blocks: Vec<Block>,
397 },
398 DocumentSection {
400 outline: OutlineTrail,
402 section: Section,
404 },
405 DocumentEntry {
407 outline: OutlineTrail,
409 entry: Block,
412 },
413}
414
415impl ExcerptSelection {
416 #[must_use]
418 pub const fn outline(&self) -> &OutlineTrail {
419 match self {
420 Self::Tldr { outline, .. }
421 | Self::DocumentRoot { outline, .. }
422 | Self::DocumentSection { outline, .. }
423 | Self::DocumentEntry { outline, .. } => outline,
424 }
425 }
426}
427
428#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
430#[serde(rename_all = "camelCase", deny_unknown_fields)]
431pub struct OutlineTrail {
432 #[serde(default, skip_serializing_if = "Vec::is_empty")]
434 pub ancestors: Vec<OutlineReference>,
435 pub node: OutlineNodeReference,
437}
438
439impl OutlineTrail {
440 #[must_use]
442 pub fn path(&self) -> &str {
443 self.node.path()
444 }
445
446 #[must_use]
448 pub fn title(&self) -> &str {
449 self.node.title()
450 }
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
455#[serde(rename_all = "camelCase", deny_unknown_fields)]
456pub struct OutlineReference {
457 pub path: NodePath,
459 pub id: NodeId,
461 pub title: String,
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
467#[serde(
468 tag = "kind",
469 rename_all = "kebab-case",
470 rename_all_fields = "camelCase",
471 deny_unknown_fields
472)]
473pub enum OutlineNodeReference {
474 Tldr {
476 path: NodePath,
478 id: NodeId,
480 title: String,
482 },
483 DocumentRoot {
485 path: NodePath,
487 id: NodeId,
489 title: String,
491 },
492 DocumentSection {
494 path: NodePath,
496 id: NodeId,
498 title: String,
500 },
501 DocumentEntry {
503 path: NodePath,
505 id: NodeId,
507 title: String,
509 entry_kind: EntryKind,
511 case: NameCase,
513 names: Vec<String>,
515 },
516}
517
518impl OutlineNodeReference {
519 #[must_use]
521 pub fn path(&self) -> &str {
522 match self {
523 Self::Tldr { path, .. }
524 | Self::DocumentRoot { path, .. }
525 | Self::DocumentSection { path, .. }
526 | Self::DocumentEntry { path, .. } => path,
527 }
528 }
529
530 #[must_use]
532 pub fn id(&self) -> &str {
533 match self {
534 Self::Tldr { id, .. }
535 | Self::DocumentRoot { id, .. }
536 | Self::DocumentSection { id, .. }
537 | Self::DocumentEntry { id, .. } => id,
538 }
539 }
540
541 #[must_use]
543 pub fn title(&self) -> &str {
544 match self {
545 Self::Tldr { title, .. }
546 | Self::DocumentRoot { title, .. }
547 | Self::DocumentSection { title, .. }
548 | Self::DocumentEntry { title, .. } => title,
549 }
550 }
551}