1use ragfs_core::SearchFilter;
4
5#[derive(Debug, Clone)]
7pub struct ParsedQuery {
8 pub text: String,
10 pub filters: Vec<SearchFilter>,
12 pub limit: usize,
14}
15
16pub struct QueryParser {
18 default_limit: usize,
20}
21
22impl QueryParser {
23 #[must_use]
25 pub fn new(default_limit: usize) -> Self {
26 Self { default_limit }
27 }
28
29 #[must_use]
37 pub fn parse(&self, query: &str) -> ParsedQuery {
38 let mut text_parts = Vec::new();
39 let mut filters = Vec::new();
40 let mut limit = self.default_limit;
41
42 for part in query.split_whitespace() {
43 if let Some((key, value)) = part.split_once(':') {
44 match key.to_lowercase().as_str() {
45 "lang" | "language" => {
46 filters.push(SearchFilter::Language(value.to_string()));
47 }
48 "path" => {
49 if value.contains('*') {
50 filters.push(SearchFilter::PathGlob(value.to_string()));
51 } else {
52 filters.push(SearchFilter::PathPrefix(value.to_string()));
53 }
54 }
55 "type" | "mime" => {
56 filters.push(SearchFilter::MimeType(value.to_string()));
57 }
58 "limit" => {
59 if let Ok(n) = value.parse() {
60 limit = n;
61 }
62 }
63 "depth" => {
64 if let Ok(n) = value.parse() {
65 filters.push(SearchFilter::MaxDepth(n));
66 }
67 }
68 _ => {
69 text_parts.push(part);
71 }
72 }
73 } else {
74 text_parts.push(part);
75 }
76 }
77
78 ParsedQuery {
79 text: text_parts.join(" "),
80 filters,
81 limit,
82 }
83 }
84}
85
86impl Default for QueryParser {
87 fn default() -> Self {
88 Self::new(10)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_parse_simple() {
98 let parser = QueryParser::default();
99 let result = parser.parse("how to implement auth");
100
101 assert_eq!(result.text, "how to implement auth");
102 assert!(result.filters.is_empty());
103 assert_eq!(result.limit, 10);
104 }
105
106 #[test]
107 fn test_parse_with_filters() {
108 let parser = QueryParser::default();
109 let result = parser.parse("authentication lang:rust path:src/** limit:5");
110
111 assert_eq!(result.text, "authentication");
112 assert_eq!(result.filters.len(), 2);
113 assert_eq!(result.limit, 5);
114 }
115
116 #[test]
117 fn test_parse_empty_query() {
118 let parser = QueryParser::default();
119 let result = parser.parse("");
120
121 assert_eq!(result.text, "");
122 assert!(result.filters.is_empty());
123 assert_eq!(result.limit, 10);
124 }
125
126 #[test]
127 fn test_parse_language_filter() {
128 let parser = QueryParser::default();
129 let result = parser.parse("lang:rust");
130
131 assert_eq!(result.text, "");
132 assert_eq!(result.filters.len(), 1);
133 assert!(matches!(&result.filters[0], SearchFilter::Language(l) if l == "rust"));
134 }
135
136 #[test]
137 fn test_parse_path_prefix() {
138 let parser = QueryParser::default();
139 let result = parser.parse("path:src/lib");
140
141 assert_eq!(result.filters.len(), 1);
142 assert!(matches!(&result.filters[0], SearchFilter::PathPrefix(p) if p == "src/lib"));
143 }
144
145 #[test]
146 fn test_parse_depth_filter() {
147 let parser = QueryParser::default();
148 let result = parser.parse("depth:2 search term");
149
150 assert_eq!(result.text, "search term");
151 assert_eq!(result.filters.len(), 1);
152 assert!(matches!(&result.filters[0], SearchFilter::MaxDepth(2)));
153 }
154
155 #[test]
156 fn test_parse_invalid_limit() {
157 let parser = QueryParser::default();
158 let result = parser.parse("limit:abc search");
159
160 assert_eq!(result.text, "search");
161 assert_eq!(result.limit, 10); }
163
164 #[test]
165 fn test_parse_unknown_filter_as_text() {
166 let parser = QueryParser::default();
167 let result = parser.parse("unknown:value search");
168
169 assert_eq!(result.text, "unknown:value search");
170 assert!(result.filters.is_empty());
171 }
172}