1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use mant_ir::{ResolvedContent, TldrDocument};
7
8use crate::{
9 ContentSelector, DocumentAddress, DocumentResponse, EntryProjection,
10 MAX_DOCUMENT_SELECTOR_CHARS, MAX_MANUAL_SECTION_CHARS, MAX_SEMANTIC_ENTRY_CHARS,
11 MAX_SOURCE_SELECTOR_CHARS, 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.11")]
22 V0Dot11,
23}
24
25impl QuerySchema {
26 pub const ID: &'static str = "mant.query/v0.11";
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
32pub enum RequestSchema {
33 #[serde(rename = "mant.request/v0.11")]
35 V0Dot11,
36}
37
38impl RequestSchema {
39 pub const ID: &'static str = "mant.request/v0.11";
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 #[serde(skip_serializing_if = "Option::is_none")]
107 root: Option<ContentSelector>,
108 #[serde(default)]
110 references: crate::ReferenceProjection,
111 },
112 Excerpt {
114 #[schemars(length(min = 1, max = MAX_NODE_SELECTORS))]
116 selectors: Vec<ContentSelector>,
117 },
118 Explain {
120 #[schemars(length(min = 1, max = MAX_SEMANTIC_ENTRY_CHARS))]
122 entry: String,
123 #[serde(default)]
125 options: crate::ExplanationOptions,
126 },
127 Search {
129 #[schemars(length(min = 1, max = 4096))]
131 pattern: String,
132 #[serde(default)]
134 syntax: SearchSyntax,
135 #[serde(default)]
137 case: SearchCase,
138 #[serde(default)]
140 scope: SearchScope,
141 #[serde(default)]
143 word: bool,
144 #[serde(default)]
146 #[schemars(range(max = 100))]
147 context_lines: u16,
148 #[serde(default = "default_search_limit")]
150 #[schemars(range(min = 1, max = 10000))]
151 limit: u32,
152 #[serde(default)]
154 offset: u32,
155 },
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
160#[serde(rename_all = "camelCase", deny_unknown_fields)]
161#[schemars(extend("$id" = "urn:mant:request:v0.11"))]
162pub struct QueryRequest {
163 pub schema: RequestSchema,
165 pub input: QueryInput,
167 pub view: QueryView,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
173#[serde(rename_all = "camelCase", deny_unknown_fields)]
174#[schemars(extend("$id" = "urn:mant:query:v0.11"))]
175pub struct QueryBundle {
176 pub schema: QuerySchema,
178 pub label: String,
180 #[serde(skip_serializing_if = "Option::is_none")]
183 pub address: Option<DocumentAddress>,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub document: Option<DocumentResponse>,
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub tldr: Option<TldrDocument>,
190}
191
192impl From<&ResolvedContent> for QueryBundle {
193 fn from(content: &ResolvedContent) -> Self {
194 Self {
195 schema: QuerySchema::V0Dot11,
196 label: content.label.clone(),
197 address: content.address.clone(),
198 document: content.document.as_ref().map(Into::into),
199 tldr: content.tldr.clone(),
200 }
201 }
202}
203
204impl From<QueryBundle> for ResolvedContent {
205 fn from(bundle: QueryBundle) -> Self {
206 Self {
207 label: bundle.label,
208 address: bundle.address,
209 document: bundle.document.map(Into::into),
210 tldr: bundle.tldr,
211 }
212 }
213}