things3_core/
query.rs

1//! Query builder for filtering and searching tasks
2
3use crate::models::{TaskFilters, TaskStatus, TaskType};
4use chrono::NaiveDate;
5use uuid::Uuid;
6
7/// Builder for constructing task queries with filters
8#[derive(Debug, Clone)]
9pub struct TaskQueryBuilder {
10    filters: TaskFilters,
11}
12
13impl TaskQueryBuilder {
14    /// Create a new query builder
15    #[must_use]
16    pub fn new() -> Self {
17        Self {
18            filters: TaskFilters::default(),
19        }
20    }
21
22    /// Filter by status
23    #[must_use]
24    pub const fn status(mut self, status: TaskStatus) -> Self {
25        self.filters.status = Some(status);
26        self
27    }
28
29    /// Filter by task type
30    #[must_use]
31    pub const fn task_type(mut self, task_type: TaskType) -> Self {
32        self.filters.task_type = Some(task_type);
33        self
34    }
35
36    /// Filter by project UUID
37    #[must_use]
38    pub const fn project_uuid(mut self, project_uuid: Uuid) -> Self {
39        self.filters.project_uuid = Some(project_uuid);
40        self
41    }
42
43    /// Filter by area UUID
44    #[must_use]
45    pub const fn area_uuid(mut self, area_uuid: Uuid) -> Self {
46        self.filters.area_uuid = Some(area_uuid);
47        self
48    }
49
50    /// Filter by tags
51    #[must_use]
52    pub fn tags(mut self, tags: Vec<String>) -> Self {
53        self.filters.tags = Some(tags);
54        self
55    }
56
57    /// Filter by start date range
58    #[must_use]
59    pub const fn start_date_range(
60        mut self,
61        from: Option<NaiveDate>,
62        to: Option<NaiveDate>,
63    ) -> Self {
64        self.filters.start_date_from = from;
65        self.filters.start_date_to = to;
66        self
67    }
68
69    /// Filter by deadline range
70    #[must_use]
71    pub const fn deadline_range(mut self, from: Option<NaiveDate>, to: Option<NaiveDate>) -> Self {
72        self.filters.deadline_from = from;
73        self.filters.deadline_to = to;
74        self
75    }
76
77    /// Add search query
78    #[must_use]
79    pub fn search(mut self, query: &str) -> Self {
80        self.filters.search_query = Some(query.to_string());
81        self
82    }
83
84    /// Set limit
85    #[must_use]
86    pub const fn limit(mut self, limit: usize) -> Self {
87        self.filters.limit = Some(limit);
88        self
89    }
90
91    /// Set offset for pagination
92    #[must_use]
93    pub const fn offset(mut self, offset: usize) -> Self {
94        self.filters.offset = Some(offset);
95        self
96    }
97
98    /// Build the final filters
99    #[must_use]
100    pub fn build(self) -> TaskFilters {
101        self.filters
102    }
103}
104
105impl Default for TaskQueryBuilder {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use chrono::NaiveDate;
115    use uuid::Uuid;
116
117    #[test]
118    fn test_task_query_builder_new() {
119        let builder = TaskQueryBuilder::new();
120        let filters = builder.build();
121
122        assert!(filters.status.is_none());
123        assert!(filters.task_type.is_none());
124        assert!(filters.project_uuid.is_none());
125        assert!(filters.area_uuid.is_none());
126        assert!(filters.tags.is_none());
127        assert!(filters.start_date_from.is_none());
128        assert!(filters.start_date_to.is_none());
129        assert!(filters.deadline_from.is_none());
130        assert!(filters.deadline_to.is_none());
131        assert!(filters.search_query.is_none());
132        assert!(filters.limit.is_none());
133        assert!(filters.offset.is_none());
134    }
135
136    #[test]
137    fn test_task_query_builder_default() {
138        let builder = TaskQueryBuilder::default();
139        let filters = builder.build();
140
141        assert!(filters.status.is_none());
142        assert!(filters.task_type.is_none());
143    }
144
145    #[test]
146    fn test_task_query_builder_status() {
147        let builder = TaskQueryBuilder::new().status(TaskStatus::Completed);
148        let filters = builder.build();
149
150        assert_eq!(filters.status, Some(TaskStatus::Completed));
151    }
152
153    #[test]
154    fn test_task_query_builder_task_type() {
155        let builder = TaskQueryBuilder::new().task_type(TaskType::Project);
156        let filters = builder.build();
157
158        assert_eq!(filters.task_type, Some(TaskType::Project));
159    }
160
161    #[test]
162    fn test_task_query_builder_project_uuid() {
163        let uuid = Uuid::new_v4();
164        let builder = TaskQueryBuilder::new().project_uuid(uuid);
165        let filters = builder.build();
166
167        assert_eq!(filters.project_uuid, Some(uuid));
168    }
169
170    #[test]
171    fn test_task_query_builder_area_uuid() {
172        let uuid = Uuid::new_v4();
173        let builder = TaskQueryBuilder::new().area_uuid(uuid);
174        let filters = builder.build();
175
176        assert_eq!(filters.area_uuid, Some(uuid));
177    }
178
179    #[test]
180    fn test_task_query_builder_tags() {
181        let tags = vec!["urgent".to_string(), "important".to_string()];
182        let builder = TaskQueryBuilder::new().tags(tags.clone());
183        let filters = builder.build();
184
185        assert_eq!(filters.tags, Some(tags));
186    }
187
188    #[test]
189    fn test_task_query_builder_start_date_range() {
190        let from = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
191        let to = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
192        let builder = TaskQueryBuilder::new().start_date_range(Some(from), Some(to));
193        let filters = builder.build();
194
195        assert_eq!(filters.start_date_from, Some(from));
196        assert_eq!(filters.start_date_to, Some(to));
197    }
198
199    #[test]
200    fn test_task_query_builder_deadline_range() {
201        let from = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
202        let to = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
203        let builder = TaskQueryBuilder::new().deadline_range(Some(from), Some(to));
204        let filters = builder.build();
205
206        assert_eq!(filters.deadline_from, Some(from));
207        assert_eq!(filters.deadline_to, Some(to));
208    }
209
210    #[test]
211    fn test_task_query_builder_search() {
212        let query = "test search";
213        let builder = TaskQueryBuilder::new().search(query);
214        let filters = builder.build();
215
216        assert_eq!(filters.search_query, Some(query.to_string()));
217    }
218
219    #[test]
220    fn test_task_query_builder_limit() {
221        let builder = TaskQueryBuilder::new().limit(50);
222        let filters = builder.build();
223
224        assert_eq!(filters.limit, Some(50));
225    }
226
227    #[test]
228    fn test_task_query_builder_offset() {
229        let builder = TaskQueryBuilder::new().offset(10);
230        let filters = builder.build();
231
232        assert_eq!(filters.offset, Some(10));
233    }
234
235    #[test]
236    fn test_task_query_builder_chaining() {
237        let uuid = Uuid::new_v4();
238        let tags = vec!["urgent".to_string()];
239        let from = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
240        let to = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
241
242        let builder = TaskQueryBuilder::new()
243            .status(TaskStatus::Incomplete)
244            .task_type(TaskType::Todo)
245            .project_uuid(uuid)
246            .tags(tags.clone())
247            .start_date_range(Some(from), Some(to))
248            .search("test")
249            .limit(25)
250            .offset(5);
251
252        let filters = builder.build();
253
254        assert_eq!(filters.status, Some(TaskStatus::Incomplete));
255        assert_eq!(filters.task_type, Some(TaskType::Todo));
256        assert_eq!(filters.project_uuid, Some(uuid));
257        assert_eq!(filters.tags, Some(tags));
258        assert_eq!(filters.start_date_from, Some(from));
259        assert_eq!(filters.start_date_to, Some(to));
260        assert_eq!(filters.search_query, Some("test".to_string()));
261        assert_eq!(filters.limit, Some(25));
262        assert_eq!(filters.offset, Some(5));
263    }
264}