Skip to main content

sift_core/query/
spec.rs

1bitflags::bitflags! {
2    /// Flags modifying how a query is interpreted by the search engine and index layer.
3    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
4    pub struct QueryFlags: u8 {
5        const FIXED_STRINGS    = 1 << 0;
6        const CASE_INSENSITIVE = 1 << 1;
7        const WORD_REGEXP      = 1 << 2;
8        const LINE_REGEXP      = 1 << 3;
9        const INVERT_MATCH     = 1 << 4;
10    }
11}
12
13/// Index-agnostic query description.
14///
15/// Describes what the user is searching for (patterns + flags) without
16/// referencing any specific index type. Each index kind interprets the
17/// spec independently to narrow candidates.
18#[derive(Debug, Clone)]
19pub struct QuerySpec<'a> {
20    pub patterns: &'a [String],
21    pub flags: QueryFlags,
22}
23
24impl QuerySpec<'_> {
25    #[must_use]
26    pub const fn fixed_strings(&self) -> bool {
27        self.flags.contains(QueryFlags::FIXED_STRINGS)
28    }
29
30    #[must_use]
31    pub const fn case_insensitive(&self) -> bool {
32        self.flags.contains(QueryFlags::CASE_INSENSITIVE)
33    }
34
35    #[must_use]
36    pub const fn word_regexp(&self) -> bool {
37        self.flags.contains(QueryFlags::WORD_REGEXP)
38    }
39
40    #[must_use]
41    pub const fn line_regexp(&self) -> bool {
42        self.flags.contains(QueryFlags::LINE_REGEXP)
43    }
44
45    #[must_use]
46    pub const fn invert_match(&self) -> bool {
47        self.flags.contains(QueryFlags::INVERT_MATCH)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn empty_flags_all_return_false() {
57        let spec = QuerySpec {
58            patterns: &[],
59            flags: QueryFlags::empty(),
60        };
61        assert!(!spec.fixed_strings());
62        assert!(!spec.case_insensitive());
63        assert!(!spec.word_regexp());
64        assert!(!spec.line_regexp());
65        assert!(!spec.invert_match());
66    }
67
68    #[test]
69    fn fixed_strings_flag_returns_true() {
70        let spec = QuerySpec {
71            patterns: &[],
72            flags: QueryFlags::FIXED_STRINGS,
73        };
74        assert!(spec.fixed_strings());
75    }
76
77    #[test]
78    fn case_insensitive_flag_returns_true() {
79        let spec = QuerySpec {
80            patterns: &[],
81            flags: QueryFlags::CASE_INSENSITIVE,
82        };
83        assert!(spec.case_insensitive());
84    }
85
86    #[test]
87    fn word_regexp_flag_returns_true() {
88        let spec = QuerySpec {
89            patterns: &[],
90            flags: QueryFlags::WORD_REGEXP,
91        };
92        assert!(spec.word_regexp());
93    }
94
95    #[test]
96    fn line_regexp_flag_returns_true() {
97        let spec = QuerySpec {
98            patterns: &[],
99            flags: QueryFlags::LINE_REGEXP,
100        };
101        assert!(spec.line_regexp());
102    }
103
104    #[test]
105    fn invert_match_flag_returns_true() {
106        let spec = QuerySpec {
107            patterns: &[],
108            flags: QueryFlags::INVERT_MATCH,
109        };
110        assert!(spec.invert_match());
111    }
112
113    #[test]
114    fn multiple_flags_all_return_true() {
115        let mut flags = QueryFlags::empty();
116        flags |= QueryFlags::FIXED_STRINGS;
117        flags |= QueryFlags::CASE_INSENSITIVE;
118        flags |= QueryFlags::WORD_REGEXP;
119        let spec = QuerySpec {
120            patterns: &["test".to_string()],
121            flags,
122        };
123        assert!(spec.fixed_strings());
124        assert!(spec.case_insensitive());
125        assert!(spec.word_regexp());
126        assert!(!spec.line_regexp());
127        assert!(!spec.invert_match());
128    }
129}