1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use mant_ir::{ResolvedContent, TldrDocument};
7
8use crate::{
9 DocumentAddress, DocumentResponse, NodeSelector, OutlineDetail, SearchCase, SearchScope,
10 SearchSyntax, default_search_limit,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15pub enum QuerySchema {
16 #[serde(rename = "mant.query/v0.9")]
18 V0Dot9,
19}
20
21impl QuerySchema {
22 pub const ID: &'static str = "mant.query/v0.9";
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
28pub enum RequestSchema {
29 #[serde(rename = "mant.request/v0.9")]
31 V0Dot9,
32}
33
34impl RequestSchema {
35 pub const ID: &'static str = "mant.request/v0.9";
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
41#[serde(
42 tag = "kind",
43 rename_all = "kebab-case",
44 rename_all_fields = "camelCase",
45 deny_unknown_fields
46)]
47pub enum QueryInput {
48 Document {
51 selector: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 source: Option<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 manual_section: Option<String>,
59 },
60 File {
62 path: String,
64 format: InputFormat,
66 },
67}
68
69#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71#[serde(rename_all = "kebab-case")]
72pub enum InputFormat {
73 #[default]
75 Auto,
76 Markdown,
78 Roff,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
84#[serde(
85 tag = "kind",
86 rename_all = "kebab-case",
87 rename_all_fields = "camelCase",
88 deny_unknown_fields
89)]
90pub enum QueryView {
91 Full {},
93 Outline {
95 detail: OutlineDetail,
97 },
98 Excerpt {
100 #[schemars(length(min = 1))]
102 selectors: Vec<NodeSelector>,
103 },
104 Explain {
106 #[schemars(length(min = 1))]
108 entry: String,
109 },
110 Search {
112 #[schemars(length(min = 1, max = 4096))]
114 pattern: String,
115 #[serde(default)]
117 syntax: SearchSyntax,
118 #[serde(default)]
120 case: SearchCase,
121 #[serde(default)]
123 scope: SearchScope,
124 #[serde(default)]
126 word: bool,
127 #[serde(default)]
129 #[schemars(range(max = 100))]
130 context_lines: u16,
131 #[serde(default = "default_search_limit")]
133 #[schemars(range(min = 1, max = 10000))]
134 limit: u32,
135 #[serde(default)]
137 offset: u32,
138 },
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
143#[serde(rename_all = "camelCase", deny_unknown_fields)]
144#[schemars(extend("$id" = "urn:mant:request:v0.9"))]
145pub struct QueryRequest {
146 pub schema: RequestSchema,
148 pub input: QueryInput,
150 pub view: QueryView,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
156#[serde(rename_all = "camelCase")]
157#[schemars(extend("$id" = "urn:mant:query:v0.9"))]
158pub struct QueryBundle {
159 pub schema: QuerySchema,
161 pub label: String,
163 #[serde(skip_serializing_if = "Option::is_none")]
166 pub address: Option<DocumentAddress>,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub document: Option<DocumentResponse>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub tldr: Option<TldrDocument>,
173}
174
175impl From<&ResolvedContent> for QueryBundle {
176 fn from(content: &ResolvedContent) -> Self {
177 Self {
178 schema: QuerySchema::V0Dot9,
179 label: content.label.clone(),
180 address: content.address.clone(),
181 document: content.document.as_ref().map(Into::into),
182 tldr: content.tldr.clone(),
183 }
184 }
185}
186
187impl From<QueryBundle> for ResolvedContent {
188 fn from(bundle: QueryBundle) -> Self {
189 Self {
190 label: bundle.label,
191 address: bundle.address,
192 document: bundle.document.map(Into::into),
193 tldr: bundle.tldr,
194 }
195 }
196}