1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use mant_ir::{ResolvedContent, TldrDocument};
7
8use crate::{
9 DocumentAddress, DocumentResponse, EntryProjection, MAX_DOCUMENT_SELECTOR_CHARS,
10 MAX_MANUAL_SECTION_CHARS, MAX_SEMANTIC_ENTRY_CHARS, MAX_SOURCE_SELECTOR_CHARS, NodeSelector,
11 SearchCase, SearchScope, SearchSyntax, default_search_limit,
12};
13
14pub const MAX_NODE_SELECTORS: usize = 16;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
19pub enum QuerySchema {
20 #[serde(rename = "mant.query/v0.10")]
22 V0Dot10,
23}
24
25impl QuerySchema {
26 pub const ID: &'static str = "mant.query/v0.10";
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
32pub enum RequestSchema {
33 #[serde(rename = "mant.request/v0.10")]
35 V0Dot10,
36}
37
38impl RequestSchema {
39 pub const ID: &'static str = "mant.request/v0.10";
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
45#[serde(
46 tag = "kind",
47 rename_all = "kebab-case",
48 rename_all_fields = "camelCase",
49 deny_unknown_fields
50)]
51pub enum QueryInput {
52 Document {
55 #[schemars(length(min = 1, max = MAX_DOCUMENT_SELECTOR_CHARS))]
57 selector: String,
58 #[schemars(length(min = 1, max = MAX_SOURCE_SELECTOR_CHARS))]
60 #[serde(skip_serializing_if = "Option::is_none")]
61 source: Option<String>,
62 #[schemars(length(min = 1, max = MAX_MANUAL_SECTION_CHARS))]
64 #[serde(skip_serializing_if = "Option::is_none")]
65 manual_section: Option<String>,
66 },
67 File {
69 path: String,
71 format: InputFormat,
73 },
74}
75
76#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78#[serde(rename_all = "kebab-case")]
79pub enum InputFormat {
80 #[default]
82 Auto,
83 Markdown,
85 Roff,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
91#[serde(
92 tag = "kind",
93 rename_all = "kebab-case",
94 rename_all_fields = "camelCase",
95 deny_unknown_fields
96)]
97pub enum QueryView {
98 Full {},
100 Outline {
102 #[serde(default)]
104 entries: EntryProjection,
105 #[schemars(length(min = 1, max = MAX_SEMANTIC_ENTRY_CHARS))]
107 #[serde(skip_serializing_if = "Option::is_none")]
108 root: Option<NodeSelector>,
109 },
110 Excerpt {
112 #[schemars(length(min = 1, max = MAX_NODE_SELECTORS))]
114 selectors: Vec<NodeSelector>,
115 },
116 Explain {
118 #[schemars(length(min = 1, max = MAX_SEMANTIC_ENTRY_CHARS))]
120 entry: String,
121 },
122 Search {
124 #[schemars(length(min = 1, max = 4096))]
126 pattern: String,
127 #[serde(default)]
129 syntax: SearchSyntax,
130 #[serde(default)]
132 case: SearchCase,
133 #[serde(default)]
135 scope: SearchScope,
136 #[serde(default)]
138 word: bool,
139 #[serde(default)]
141 #[schemars(range(max = 100))]
142 context_lines: u16,
143 #[serde(default = "default_search_limit")]
145 #[schemars(range(min = 1, max = 10000))]
146 limit: u32,
147 #[serde(default)]
149 offset: u32,
150 },
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
155#[serde(rename_all = "camelCase", deny_unknown_fields)]
156#[schemars(extend("$id" = "urn:mant:request:v0.10"))]
157pub struct QueryRequest {
158 pub schema: RequestSchema,
160 pub input: QueryInput,
162 pub view: QueryView,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
168#[serde(rename_all = "camelCase")]
169#[schemars(extend("$id" = "urn:mant:query:v0.10"))]
170pub struct QueryBundle {
171 pub schema: QuerySchema,
173 pub label: String,
175 #[serde(skip_serializing_if = "Option::is_none")]
178 pub address: Option<DocumentAddress>,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub document: Option<DocumentResponse>,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub tldr: Option<TldrDocument>,
185}
186
187impl From<&ResolvedContent> for QueryBundle {
188 fn from(content: &ResolvedContent) -> Self {
189 Self {
190 schema: QuerySchema::V0Dot10,
191 label: content.label.clone(),
192 address: content.address.clone(),
193 document: content.document.as_ref().map(Into::into),
194 tldr: content.tldr.clone(),
195 }
196 }
197}
198
199impl From<QueryBundle> for ResolvedContent {
200 fn from(bundle: QueryBundle) -> Self {
201 Self {
202 label: bundle.label,
203 address: bundle.address,
204 document: bundle.document.map(Into::into),
205 tldr: bundle.tldr,
206 }
207 }
208}