1use super::{Domain, Memory, MemoryStatus, Namespace};
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum SearchMode {
9 Vector,
11 Text,
13 #[default]
15 Hybrid,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum DetailLevel {
23 Light,
26 #[default]
28 Medium,
29 Everything,
31}
32
33impl DetailLevel {
34 #[must_use]
36 pub const fn as_str(&self) -> &'static str {
37 match self {
38 Self::Light => "light",
39 Self::Medium => "medium",
40 Self::Everything => "everything",
41 }
42 }
43
44 #[must_use]
46 pub fn parse(s: &str) -> Option<Self> {
47 match s.to_lowercase().as_str() {
48 "light" | "minimal" | "frontmatter" => Some(Self::Light),
49 "medium" | "summary" | "default" => Some(Self::Medium),
50 "everything" | "full" | "all" => Some(Self::Everything),
51 _ => None,
52 }
53 }
54
55 #[must_use]
57 pub const fn content_length(&self) -> Option<usize> {
58 match self {
59 Self::Light => Some(0), Self::Medium => Some(200), Self::Everything => None, }
63 }
64}
65
66impl fmt::Display for DetailLevel {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "{}", self.as_str())
69 }
70}
71
72impl SearchMode {
73 #[must_use]
75 pub const fn as_str(&self) -> &'static str {
76 match self {
77 Self::Vector => "vector",
78 Self::Text => "text",
79 Self::Hybrid => "hybrid",
80 }
81 }
82}
83
84impl fmt::Display for SearchMode {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "{}", self.as_str())
87 }
88}
89
90#[derive(Debug, Clone, Default)]
92pub struct SearchFilter {
93 pub namespaces: Vec<Namespace>,
95 pub domains: Vec<Domain>,
97 pub statuses: Vec<MemoryStatus>,
99 pub tags: Vec<String>,
101 pub tags_any: Vec<String>,
103 pub excluded_tags: Vec<String>,
105 pub source_pattern: Option<String>,
107 pub project_id: Option<String>,
109 pub branch: Option<String>,
111 pub file_path: Option<String>,
113 pub created_after: Option<u64>,
115 pub created_before: Option<u64>,
117 pub min_score: Option<f32>,
119 pub include_tombstoned: bool,
121 pub entity_names: Vec<String>,
124 #[cfg(feature = "group-scope")]
127 pub group_ids: Vec<String>,
128}
129
130impl SearchFilter {
131 #[must_use]
133 pub const fn new() -> Self {
134 Self {
135 namespaces: Vec::new(),
136 domains: Vec::new(),
137 statuses: Vec::new(),
138 tags: Vec::new(),
139 tags_any: Vec::new(),
140 excluded_tags: Vec::new(),
141 source_pattern: None,
142 project_id: None,
143 branch: None,
144 file_path: None,
145 created_after: None,
146 created_before: None,
147 min_score: None,
148 include_tombstoned: false,
149 entity_names: Vec::new(),
150 #[cfg(feature = "group-scope")]
151 group_ids: Vec::new(),
152 }
153 }
154
155 #[must_use]
157 pub fn with_namespace(mut self, namespace: Namespace) -> Self {
158 self.namespaces.push(namespace);
159 self
160 }
161
162 #[must_use]
164 pub fn with_domain(mut self, domain: Domain) -> Self {
165 self.domains.push(domain);
166 self
167 }
168
169 #[must_use]
171 pub fn with_status(mut self, status: MemoryStatus) -> Self {
172 self.statuses.push(status);
173 self
174 }
175
176 #[must_use]
178 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
179 self.tags.push(tag.into());
180 self
181 }
182
183 #[must_use]
185 pub fn with_tag_any(mut self, tag: impl Into<String>) -> Self {
186 self.tags_any.push(tag.into());
187 self
188 }
189
190 #[must_use]
192 pub fn with_excluded_tag(mut self, tag: impl Into<String>) -> Self {
193 self.excluded_tags.push(tag.into());
194 self
195 }
196
197 #[must_use]
199 pub fn with_source_pattern(mut self, pattern: impl Into<String>) -> Self {
200 self.source_pattern = Some(pattern.into());
201 self
202 }
203
204 #[must_use]
206 pub fn with_project_id(mut self, project_id: impl Into<String>) -> Self {
207 self.project_id = Some(project_id.into());
208 self
209 }
210
211 #[must_use]
213 pub fn with_branch(mut self, branch: impl Into<String>) -> Self {
214 self.branch = Some(branch.into());
215 self
216 }
217
218 #[must_use]
220 pub fn with_file_path(mut self, file_path: impl Into<String>) -> Self {
221 self.file_path = Some(file_path.into());
222 self
223 }
224
225 #[must_use]
227 pub const fn with_min_score(mut self, score: f32) -> Self {
228 self.min_score = Some(score);
229 self
230 }
231
232 #[must_use]
234 pub const fn with_created_after(mut self, timestamp: u64) -> Self {
235 self.created_after = Some(timestamp);
236 self
237 }
238
239 #[must_use]
241 pub const fn with_created_before(mut self, timestamp: u64) -> Self {
242 self.created_before = Some(timestamp);
243 self
244 }
245
246 #[must_use]
248 pub const fn with_include_tombstoned(mut self, include: bool) -> Self {
249 self.include_tombstoned = include;
250 self
251 }
252
253 #[must_use]
255 #[allow(clippy::missing_const_for_fn)] pub fn is_empty(&self) -> bool {
257 let base_empty = self.namespaces.is_empty()
258 && self.domains.is_empty()
259 && self.statuses.is_empty()
260 && self.tags.is_empty()
261 && self.tags_any.is_empty()
262 && self.excluded_tags.is_empty()
263 && self.source_pattern.is_none()
264 && self.project_id.is_none()
265 && self.branch.is_none()
266 && self.file_path.is_none()
267 && self.created_after.is_none()
268 && self.created_before.is_none()
269 && self.min_score.is_none()
270 && self.entity_names.is_empty();
271
272 #[cfg(feature = "group-scope")]
273 {
274 base_empty && self.group_ids.is_empty()
275 }
276 #[cfg(not(feature = "group-scope"))]
277 {
278 base_empty
279 }
280 }
281
282 #[must_use]
285 pub fn with_entity(mut self, entity: impl Into<String>) -> Self {
286 self.entity_names.push(entity.into());
287 self
288 }
289
290 #[must_use]
292 pub fn with_entities(mut self, entities: impl IntoIterator<Item = impl Into<String>>) -> Self {
293 self.entity_names
294 .extend(entities.into_iter().map(Into::into));
295 self
296 }
297
298 #[cfg(feature = "group-scope")]
302 #[must_use]
303 pub fn with_group_id(mut self, group_id: impl Into<String>) -> Self {
304 self.group_ids.push(group_id.into());
305 self
306 }
307
308 #[cfg(feature = "group-scope")]
310 #[must_use]
311 pub fn with_group_ids(
312 mut self,
313 group_ids: impl IntoIterator<Item = impl Into<String>>,
314 ) -> Self {
315 self.group_ids.extend(group_ids.into_iter().map(Into::into));
316 self
317 }
318}
319
320#[derive(Debug, Clone)]
322pub struct SearchResult {
323 pub memories: Vec<SearchHit>,
325 pub total_count: usize,
327 pub mode: SearchMode,
329 pub execution_time_ms: u64,
331}
332
333#[derive(Debug, Clone)]
335pub struct SearchHit {
336 pub memory: Memory,
338 pub score: f32,
342 pub raw_score: f32,
346 pub vector_score: Option<f32>,
348 pub bm25_score: Option<f32>,
350}