connpass_rs/query/
builder.rs

1use helper::*;
2
3use crate::errors::ConnpassCliError;
4
5use super::{
6    types::{FetchCountRange, FormatJson},
7    validator::Validator,
8    OrderOption, Query,
9};
10
11/// Utility builder for building `query::Query`.
12pub struct QueryBuilder {
13    event_id: Option<Vec<u32>>,
14    keyword: Option<Vec<String>>,
15    keyword_or: Option<Vec<String>>,
16    ym: Option<Vec<u32>>,
17    ymd: Option<Vec<u32>>,
18    nickname: Option<Vec<String>>,
19    owner_nickname: Option<Vec<String>>,
20    series_id: Option<Vec<u32>>,
21    start: Option<u32>,
22    order: Option<OrderOption>,
23    count: Option<FetchCountRange>,
24    format: Option<FormatJson>,
25}
26
27impl Default for QueryBuilder {
28    fn default() -> Self {
29        Self {
30            event_id: None,
31            keyword: None,
32            keyword_or: None,
33            ym: None,
34            ymd: None,
35            nickname: None,
36            owner_nickname: None,
37            series_id: None,
38            start: None,
39            order: None,
40            count: None,
41            format: None,
42        }
43    }
44}
45
46/// An implementation for QueryBuilder.
47/// There are two function types:
48/// 1. functions that can accept a single argument.
49/// 2. functions that can accept a `Vec` type argument.
50///
51/// The former ones simply add the accepted value to this builder,
52/// but the latter ones always _replace_ the value in placed in this builder by the accepted one.
53impl QueryBuilder {
54    /// Initializes `QueryBuilder`.
55    pub fn begin() -> Self {
56        QueryBuilder::default()
57    }
58
59    pub fn event_ids(mut self, ids: Vec<u32>) -> Self {
60        self.event_id = Some(ids);
61        self
62    }
63
64    pub fn event_id(mut self, id: u32) -> Self {
65        self.event_id = push_or_create(self.event_id, id);
66        self
67    }
68
69    pub fn keywords(mut self, keywords: Vec<String>) -> Self {
70        self.keyword = Some(keywords);
71        self
72    }
73
74    pub fn keyword(mut self, keyword: impl Into<String>) -> Self {
75        self.keyword = push_or_create(self.keyword, keyword.into());
76        self
77    }
78
79    pub fn keywords_or(mut self, keywords: Vec<String>) -> Self {
80        self.keyword_or = Some(keywords);
81        self
82    }
83
84    pub fn keyword_or(mut self, keyword: impl Into<String>) -> Self {
85        self.keyword_or = push_or_create(self.keyword_or, keyword.into());
86        self
87    }
88
89    pub fn yms(mut self, ym: Vec<u32>) -> Self {
90        self.ym = Some(ym);
91        self
92    }
93
94    pub fn ym(mut self, ym: u32) -> Self {
95        self.ym = push_or_create(self.ym, ym);
96        self
97    }
98
99    pub fn ymds(mut self, ymd: Vec<u32>) -> Self {
100        self.ymd = Some(ymd);
101        self
102    }
103
104    pub fn ymd(mut self, ymd: u32) -> Self {
105        self.ymd = push_or_create(self.ymd, ymd);
106        self
107    }
108
109    pub fn nicknames(mut self, nickname: Vec<String>) -> Self {
110        self.nickname = Some(nickname);
111        self
112    }
113
114    pub fn nickname(mut self, nickname: impl Into<String>) -> Self {
115        self.nickname = push_or_create(self.nickname, nickname.into());
116        self
117    }
118
119    pub fn owner_nicknames(mut self, owner_nickname: Vec<String>) -> Self {
120        self.owner_nickname = Some(owner_nickname);
121        self
122    }
123
124    pub fn owner_nickname(mut self, owner_nickname: impl Into<String>) -> Self {
125        self.owner_nickname = push_or_create(self.owner_nickname, owner_nickname.into());
126        self
127    }
128
129    pub fn series_ids(mut self, series_ids: Vec<u32>) -> Self {
130        self.series_id = Some(series_ids);
131        self
132    }
133
134    pub fn series_id(mut self, series_id: u32) -> Self {
135        self.series_id = push_or_create(self.series_id, series_id);
136        self
137    }
138
139    pub fn start(mut self, start: u32) -> Self {
140        self.start = Some(start);
141        self
142    }
143
144    pub fn order(mut self, order: OrderOption) -> Self {
145        self.order = Some(order);
146        self
147    }
148
149    pub fn count(mut self, count: u8) -> Self {
150        self.count = Some(FetchCountRange(count));
151        self
152    }
153
154    pub fn format(mut self, format: impl Into<String>) -> Self {
155        self.format = Some(FormatJson(format.into()));
156        self
157    }
158
159    /// Converts from `QueryBuilder` to `Query` with some validation checks.
160    /// The following checks run in this function:
161    /// 1. validate the `count` value in range of 0 to 100.
162    /// 2. validate if the `format` value is just "json".
163    ///
164    /// These validation specifications are described in connpass's documentation.
165    /// Please have a look at https://connpass.com/about/api/.
166    pub fn build(self) -> Result<Query, ConnpassCliError> {
167        let mut query = Query {
168            event_id: self.event_id,
169            keyword: self.keyword,
170            keyword_or: self.keyword_or,
171            ym: self.ym,
172            ymd: self.ymd,
173            nickname: self.nickname,
174            owner_nickname: self.owner_nickname,
175            series_id: self.series_id,
176            start: self.start,
177            order: self.order,
178            ..Default::default()
179        };
180
181        if let Some(count) = self.count {
182            query.count = Some(count.validate()?.0);
183        }
184
185        if let Some(format) = self.format {
186            query.format = Some(format.validate()?.0);
187        }
188
189        Ok(query)
190    }
191}
192
193mod helper {
194    pub fn push_or_create<T>(source: Option<Vec<T>>, pushed: T) -> Option<Vec<T>> {
195        match source {
196            Some(mut xs) => {
197                xs.push(pushed);
198                Some(xs)
199            }
200            None => Some(vec![pushed]),
201        }
202    }
203}
204
205#[cfg(test)]
206mod test {
207    use crate::{
208        errors::{ConnpassCliError, ValidationError},
209        query::{types::OrderOption, Query},
210    };
211
212    use super::QueryBuilder;
213
214    #[test]
215    fn test_add_event_ids() {
216        let builder = QueryBuilder::begin().event_ids(vec![1, 2, 3]);
217        assert_eq!(
218            builder.build().unwrap(),
219            Query {
220                event_id: Some(vec![1, 2, 3]),
221                ..Default::default()
222            }
223        );
224    }
225
226    #[test]
227    fn test_add_event_id() {
228        let builder = QueryBuilder::begin().event_id(1);
229        assert_eq!(
230            builder.build().unwrap(),
231            Query {
232                event_id: Some(vec![1]),
233                ..Default::default()
234            }
235        );
236    }
237
238    #[test]
239    fn test_call_multiple_time_event_id() {
240        let builder = QueryBuilder::begin().event_id(1).event_id(2).event_id(3);
241        assert_eq!(
242            builder.build().unwrap(),
243            Query {
244                event_id: Some(vec![1, 2, 3]),
245                ..Default::default()
246            }
247        );
248    }
249
250    #[test]
251    fn test_add_keywords() {
252        let builder = QueryBuilder::begin().keywords(vec![
253            "Python".to_string(),
254            "Rust".to_string(),
255            "Swift".to_string(),
256        ]);
257        assert_eq!(
258            builder.build().unwrap(),
259            Query {
260                keyword: Some(vec![
261                    "Python".to_string(),
262                    "Rust".to_string(),
263                    "Swift".to_string()
264                ]),
265                ..Default::default()
266            }
267        );
268    }
269
270    #[test]
271    fn test_add_keyword() {
272        let builder = QueryBuilder::begin().keyword("Rust");
273        assert_eq!(
274            builder.build().unwrap(),
275            Query {
276                keyword: Some(vec!["Rust".to_string()]),
277                ..Default::default()
278            }
279        );
280    }
281
282    #[test]
283    fn test_call_multiple_time_keyword() {
284        let builder = QueryBuilder::begin()
285            .keyword("Python".to_string())
286            .keyword("Rust".to_string())
287            .keyword("Swift".to_string());
288        assert_eq!(
289            builder.build().unwrap(),
290            Query {
291                keyword: Some(vec![
292                    "Python".to_string(),
293                    "Rust".to_string(),
294                    "Swift".to_string()
295                ]),
296                ..Default::default()
297            }
298        );
299    }
300
301    #[test]
302    fn test_add_keywords_or() {
303        let builder = QueryBuilder::begin().keywords_or(vec![
304            "Python".to_string(),
305            "Rust".to_string(),
306            "Swift".to_string(),
307        ]);
308        assert_eq!(
309            builder.build().unwrap(),
310            Query {
311                keyword_or: Some(vec![
312                    "Python".to_string(),
313                    "Rust".to_string(),
314                    "Swift".to_string()
315                ]),
316                ..Default::default()
317            }
318        );
319    }
320
321    #[test]
322    fn test_add_keyword_or() {
323        let builder = QueryBuilder::begin().keyword_or("Rust");
324        assert_eq!(
325            builder.build().unwrap(),
326            Query {
327                keyword_or: Some(vec!["Rust".to_string()]),
328                ..Default::default()
329            }
330        );
331    }
332
333    #[test]
334    fn test_call_multiple_time_keyword_or() {
335        let builder = QueryBuilder::begin()
336            .keyword_or("Python".to_string())
337            .keyword_or("Rust".to_string())
338            .keyword_or("Swift".to_string());
339        assert_eq!(
340            builder.build().unwrap(),
341            Query {
342                keyword_or: Some(vec![
343                    "Python".to_string(),
344                    "Rust".to_string(),
345                    "Swift".to_string()
346                ]),
347                ..Default::default()
348            }
349        );
350    }
351
352    #[test]
353    fn test_add_yms() {
354        let builder = QueryBuilder::begin().yms(vec![202101, 202102, 202103]);
355        assert_eq!(
356            builder.build().unwrap(),
357            Query {
358                ym: Some(vec![202101, 202102, 202103]),
359                ..Default::default()
360            }
361        );
362    }
363
364    #[test]
365    fn test_add_ym() {
366        let builder = QueryBuilder::begin().ym(202101);
367        assert_eq!(
368            builder.build().unwrap(),
369            Query {
370                ym: Some(vec![202101]),
371                ..Default::default()
372            }
373        );
374    }
375
376    #[test]
377    fn test_call_multiple_time_ym() {
378        let builder = QueryBuilder::begin().ym(202101).ym(202102).ym(202103);
379        assert_eq!(
380            builder.build().unwrap(),
381            Query {
382                ym: Some(vec![202101, 202102, 202103]),
383                ..Default::default()
384            }
385        );
386    }
387
388    #[test]
389    fn test_add_ymds() {
390        let builder = QueryBuilder::begin().ymds(vec![20210101, 20210201, 20210301]);
391        assert_eq!(
392            builder.build().unwrap(),
393            Query {
394                ymd: Some(vec![20210101, 20210201, 20210301]),
395                ..Default::default()
396            }
397        );
398    }
399
400    #[test]
401    fn test_add_ymd() {
402        let builder = QueryBuilder::begin().ymd(20210101);
403        assert_eq!(
404            builder.build().unwrap(),
405            Query {
406                ymd: Some(vec![20210101]),
407                ..Default::default()
408            }
409        );
410    }
411
412    #[test]
413    fn test_call_multiple_time_ymd() {
414        let builder = QueryBuilder::begin()
415            .ymd(20210101)
416            .ymd(20210201)
417            .ymd(20210301);
418        assert_eq!(
419            builder.build().unwrap(),
420            Query {
421                ymd: Some(vec![20210101, 20210201, 20210301]),
422                ..Default::default()
423            }
424        );
425    }
426
427    #[test]
428    fn test_add_nicknames() {
429        let builder = QueryBuilder::begin().nicknames(vec![
430            "Harry".to_string(),
431            "Ron".to_string(),
432            "Hermione".to_string(),
433        ]);
434        assert_eq!(
435            builder.build().unwrap(),
436            Query {
437                nickname: Some(vec![
438                    "Harry".to_string(),
439                    "Ron".to_string(),
440                    "Hermione".to_string()
441                ]),
442                ..Default::default()
443            }
444        );
445    }
446
447    #[test]
448    fn test_add_nickname() {
449        let builder = QueryBuilder::begin().nickname("Harry");
450        assert_eq!(
451            builder.build().unwrap(),
452            Query {
453                nickname: Some(vec!["Harry".to_string()]),
454                ..Default::default()
455            }
456        );
457    }
458
459    #[test]
460    fn test_call_multiple_time_nickname() {
461        let builder = QueryBuilder::begin()
462            .nickname("Harry")
463            .nickname("Ron")
464            .nickname("Hermione");
465        assert_eq!(
466            builder.build().unwrap(),
467            Query {
468                nickname: Some(vec![
469                    "Harry".to_string(),
470                    "Ron".to_string(),
471                    "Hermione".to_string()
472                ]),
473                ..Default::default()
474            }
475        );
476    }
477
478    #[test]
479    fn test_add_owner_nicknames() {
480        let builder = QueryBuilder::begin().owner_nicknames(vec![
481            "Harry".to_string(),
482            "Ron".to_string(),
483            "Hermione".to_string(),
484        ]);
485        assert_eq!(
486            builder.build().unwrap(),
487            Query {
488                owner_nickname: Some(vec![
489                    "Harry".to_string(),
490                    "Ron".to_string(),
491                    "Hermione".to_string()
492                ]),
493                ..Default::default()
494            }
495        );
496    }
497
498    #[test]
499    fn test_add_owner_nickname() {
500        let builder = QueryBuilder::begin().owner_nickname("Harry");
501        assert_eq!(
502            builder.build().unwrap(),
503            Query {
504                owner_nickname: Some(vec!["Harry".to_string()]),
505                ..Default::default()
506            }
507        );
508    }
509
510    #[test]
511    fn test_call_multiple_time_owner_nickname() {
512        let builder = QueryBuilder::begin()
513            .owner_nickname("Harry")
514            .owner_nickname("Ron")
515            .owner_nickname("Hermione");
516        assert_eq!(
517            builder.build().unwrap(),
518            Query {
519                owner_nickname: Some(vec![
520                    "Harry".to_string(),
521                    "Ron".to_string(),
522                    "Hermione".to_string()
523                ]),
524                ..Default::default()
525            }
526        );
527    }
528
529    #[test]
530    fn test_add_series_ids() {
531        let builder = QueryBuilder::begin().series_ids(vec![1, 2, 3]);
532        assert_eq!(
533            builder.build().unwrap(),
534            Query {
535                series_id: Some(vec![1, 2, 3]),
536                ..Default::default()
537            }
538        );
539    }
540
541    #[test]
542    fn test_add_series_id() {
543        let builder = QueryBuilder::begin().series_id(1);
544        assert_eq!(
545            builder.build().unwrap(),
546            Query {
547                series_id: Some(vec![1]),
548                ..Default::default()
549            }
550        );
551    }
552
553    #[test]
554    fn test_call_multiple_time_series_id() {
555        let builder = QueryBuilder::begin().series_id(1).series_id(2).series_id(3);
556        assert_eq!(
557            builder.build().unwrap(),
558            Query {
559                series_id: Some(vec![1, 2, 3]),
560                ..Default::default()
561            }
562        );
563    }
564
565    #[test]
566    fn test_call_start() {
567        let builder = QueryBuilder::begin().start(1);
568        assert_eq!(
569            builder.build().unwrap(),
570            Query {
571                start: Some(1),
572                ..Default::default()
573            }
574        );
575    }
576
577    #[test]
578    fn test_call_order() {
579        let builder = QueryBuilder::begin().order(OrderOption::LastModifiedDate);
580        assert_eq!(
581            builder.build().unwrap(),
582            Query {
583                order: Some(OrderOption::LastModifiedDate),
584                ..Default::default()
585            }
586        );
587    }
588
589    #[test]
590    fn test_call_count() {
591        let builder = QueryBuilder::begin().count(50);
592        assert_eq!(
593            builder.build().unwrap(),
594            Query {
595                count: Some(50),
596                ..Default::default()
597            }
598        );
599    }
600
601    #[test]
602    fn test_validation_count_range() {
603        let builder = QueryBuilder::begin().count(0);
604        assert!(matches!(
605            builder.build(),
606            Err(ConnpassCliError::Validation(ValidationError::OutOfRange {
607                msg: _
608            }))
609        ));
610
611        let builder = QueryBuilder::begin().count(101);
612        assert!(matches!(
613            builder.build(),
614            Err(ConnpassCliError::Validation(ValidationError::OutOfRange {
615                msg: _
616            }))
617        ));
618    }
619
620    #[test]
621    fn test_call_format() {
622        let builder = QueryBuilder::begin().format("json");
623        assert_eq!(
624            builder.build().unwrap(),
625            Query {
626                format: Some("json".to_string()),
627                ..Default::default()
628            }
629        );
630    }
631
632    #[test]
633    fn test_validation_format() {
634        let builder = QueryBuilder::begin().format("yaml");
635        assert!(matches!(
636            builder.build(),
637            Err(ConnpassCliError::Validation(
638                ValidationError::InvalidToken { msg: _ }
639            ))
640        ));
641    }
642}