1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::{DefinitionCase, DefinitionRole, DocumentMeta, DocumentSource, SourceSpan};
7
8pub const DEFAULT_SEARCH_LIMIT: u32 = 100;
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
12#[serde(rename_all = "kebab-case")]
13pub enum SearchSyntax {
14 #[default]
15 Literal,
16 Regex,
17}
18
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
21#[serde(rename_all = "kebab-case")]
22pub enum SearchCase {
23 #[default]
24 Insensitive,
25 Sensitive,
26 Smart,
27}
28
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
31#[serde(rename_all = "kebab-case")]
32pub enum SearchScope {
33 #[default]
35 Visible,
36 Markdown,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42#[serde(rename_all = "camelCase", deny_unknown_fields)]
43pub struct SearchQuery {
44 #[schemars(length(min = 1, max = 4096))]
45 pub pattern: String,
46 #[serde(default)]
47 pub syntax: SearchSyntax,
48 #[serde(default)]
49 pub case: SearchCase,
50 #[serde(default)]
51 pub scope: SearchScope,
52 #[serde(default)]
53 pub word: bool,
54 #[serde(default)]
55 #[schemars(range(max = 100))]
56 pub context_lines: u16,
57 #[serde(default = "default_search_limit")]
58 #[schemars(range(min = 1, max = 10000))]
59 pub limit: u32,
60 #[serde(default)]
61 pub offset: u32,
62}
63
64#[must_use]
65pub const fn default_search_limit() -> u32 {
66 DEFAULT_SEARCH_LIMIT
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
71pub enum SearchSchema {
72 #[serde(rename = "mant.search/v6")]
73 V6,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78pub enum MarkdownSchema {
79 #[serde(rename = "mant.markdown/v1")]
80 V1,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
85#[serde(rename_all = "kebab-case")]
86pub enum SearchRenderFormat {
87 Markdown,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
92#[serde(rename_all = "kebab-case")]
93pub enum SearchRenderScope {
94 Full,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
99#[serde(rename_all = "camelCase")]
100pub struct SearchRender {
101 pub schema: MarkdownSchema,
102 pub format: SearchRenderFormat,
103 pub scope: SearchRenderScope,
104 pub line_base: u8,
105 pub column_base: u8,
106 pub line_count: u32,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
111#[serde(rename_all = "camelCase")]
112#[schemars(extend("$id" = "urn:mant:search:v6"))]
113pub struct QuerySearch {
114 pub schema: SearchSchema,
115 pub label: String,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub source: Option<DocumentSource>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub meta: Option<DocumentMeta>,
120 pub query: SearchQuery,
121 pub render: SearchRender,
122 pub total: u32,
123 pub returned: u32,
124 pub offset: u32,
125 pub truncated: bool,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub next_offset: Option<u32>,
128 pub matches: Vec<SearchMatch>,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
133#[serde(rename_all = "camelCase")]
134pub struct SearchMatch {
135 pub ordinal: u32,
136 pub node: SearchNode,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub section: Option<SearchSectionReference>,
139 pub matched_text: String,
140 pub markdown: SearchMarkdownRange,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub source: Option<SourceSpan>,
143 pub preview: String,
144 #[serde(default, skip_serializing_if = "Vec::is_empty")]
145 pub context: Vec<SearchContextLine>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
150#[serde(
151 tag = "kind",
152 rename_all = "kebab-case",
153 rename_all_fields = "camelCase"
154)]
155pub enum SearchNode {
156 Tldr {
157 path: String,
158 id: String,
159 title: String,
160 },
161 DocumentRoot {
162 path: String,
163 id: String,
164 title: String,
165 },
166 DocumentSection {
167 path: String,
168 id: String,
169 title: String,
170 },
171 DocumentEntry {
172 path: String,
173 id: String,
174 title: String,
175 role: DefinitionRole,
176 case: DefinitionCase,
177 names: Vec<String>,
178 },
179}
180
181impl SearchNode {
182 #[must_use]
183 pub fn path(&self) -> &str {
184 match self {
185 Self::Tldr { path, .. }
186 | Self::DocumentRoot { path, .. }
187 | Self::DocumentSection { path, .. }
188 | Self::DocumentEntry { path, .. } => path,
189 }
190 }
191
192 #[must_use]
193 pub fn title(&self) -> &str {
194 match self {
195 Self::Tldr { title, .. }
196 | Self::DocumentRoot { title, .. }
197 | Self::DocumentSection { title, .. }
198 | Self::DocumentEntry { title, .. } => title,
199 }
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
205#[serde(rename_all = "camelCase")]
206pub struct SearchSectionReference {
207 pub path: String,
208 pub id: String,
209 pub title: String,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
214#[serde(rename_all = "camelCase")]
215pub struct SearchMarkdownRange {
216 pub start_byte: u64,
217 pub end_byte: u64,
218 pub start_line: u32,
219 pub start_column: u32,
220 pub end_line: u32,
221 pub end_column: u32,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
226#[serde(rename_all = "camelCase")]
227pub struct SearchContextLine {
228 pub line: u32,
229 pub text: String,
230 pub matched: bool,
231}