Skip to main content

cipherstash_config/column/index/
array_index_mode.rs

1use bitflags::bitflags;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
4bitflags! {
5    /// Controls which array selectors are generated during JSON indexing.
6    ///
7    /// By default, no array selectors are generated (opt-in).
8    /// Users can enable specific selectors or use `ALL` for current behavior.
9    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
10    pub struct ArrayIndexMode: u8 {
11        /// No array indexing (default)
12        const NONE     = 0b0000;
13        /// Generate [@] selectors for "any item" queries
14        const ITEM     = 0b0001;
15        /// Generate [*] selectors for JSONPath wildcard queries
16        const WILDCARD = 0b0010;
17        /// Generate [n] selectors for positional queries
18        const POSITION = 0b0100;
19        /// Generate all selectors (current behavior)
20        const ALL      = Self::ITEM.bits() | Self::WILDCARD.bits() | Self::POSITION.bits();
21    }
22}
23
24impl ArrayIndexMode {
25    /// Returns true if ITEM selector should be generated
26    pub fn has_item(&self) -> bool {
27        self.contains(Self::ITEM)
28    }
29
30    /// Returns true if WILDCARD selector should be generated
31    pub fn has_wildcard(&self) -> bool {
32        self.contains(Self::WILDCARD)
33    }
34
35    /// Returns true if POSITION selector should be generated
36    pub fn has_position(&self) -> bool {
37        self.contains(Self::POSITION)
38    }
39}
40
41/// Helper struct for object-form serialization
42#[derive(Serialize, Deserialize)]
43struct ArrayIndexModeObject {
44    #[serde(default)]
45    item: bool,
46    #[serde(default)]
47    wildcard: bool,
48    #[serde(default)]
49    position: bool,
50}
51
52impl Serialize for ArrayIndexMode {
53    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
54    where
55        S: Serializer,
56    {
57        // Emit presets for ALL and NONE, object form for partial combinations
58        if *self == Self::ALL {
59            serializer.serialize_str("all")
60        } else if *self == Self::NONE {
61            serializer.serialize_str("none")
62        } else {
63            let obj = ArrayIndexModeObject {
64                item: self.has_item(),
65                wildcard: self.has_wildcard(),
66                position: self.has_position(),
67            };
68            obj.serialize(serializer)
69        }
70    }
71}
72
73impl<'de> Deserialize<'de> for ArrayIndexMode {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        use serde::de::Error;
79
80        // First try to deserialize as a string preset
81        #[derive(Deserialize)]
82        #[serde(untagged)]
83        enum ModeOrPreset {
84            Preset(String),
85            Object(ArrayIndexModeObject),
86        }
87
88        match ModeOrPreset::deserialize(deserializer)? {
89            ModeOrPreset::Preset(s) => match s.to_lowercase().as_str() {
90                "all" => Ok(Self::ALL),
91                "none" => Ok(Self::NONE),
92                other => Err(D::Error::custom(format!(
93                    "unknown preset '{}', expected 'all' or 'none'",
94                    other
95                ))),
96            },
97            ModeOrPreset::Object(obj) => {
98                let mut mode = Self::NONE;
99                if obj.item {
100                    mode |= Self::ITEM;
101                }
102                if obj.wildcard {
103                    mode |= Self::WILDCARD;
104                }
105                if obj.position {
106                    mode |= Self::POSITION;
107                }
108                Ok(mode)
109            }
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_default_is_none() {
120        let mode = ArrayIndexMode::default();
121        assert_eq!(mode, ArrayIndexMode::NONE);
122        assert!(!mode.has_item());
123        assert!(!mode.has_wildcard());
124        assert!(!mode.has_position());
125    }
126
127    #[test]
128    fn test_all_contains_all_flags() {
129        let mode = ArrayIndexMode::ALL;
130        assert!(mode.has_item());
131        assert!(mode.has_wildcard());
132        assert!(mode.has_position());
133    }
134
135    #[test]
136    fn test_individual_flags() {
137        let item = ArrayIndexMode::ITEM;
138        assert!(item.has_item());
139        assert!(!item.has_wildcard());
140        assert!(!item.has_position());
141
142        let wildcard = ArrayIndexMode::WILDCARD;
143        assert!(!wildcard.has_item());
144        assert!(wildcard.has_wildcard());
145        assert!(!wildcard.has_position());
146
147        let position = ArrayIndexMode::POSITION;
148        assert!(!position.has_item());
149        assert!(!position.has_wildcard());
150        assert!(position.has_position());
151    }
152
153    #[test]
154    fn test_flag_combinations() {
155        let combo = ArrayIndexMode::ITEM | ArrayIndexMode::WILDCARD;
156        assert!(combo.has_item());
157        assert!(combo.has_wildcard());
158        assert!(!combo.has_position());
159    }
160
161    #[test]
162    fn test_serialize_all_as_preset() {
163        let mode = ArrayIndexMode::ALL;
164        let json = serde_json::to_string(&mode).unwrap();
165        assert_eq!(json, r#""all""#);
166    }
167
168    #[test]
169    fn test_serialize_none_as_preset() {
170        let mode = ArrayIndexMode::NONE;
171        let json = serde_json::to_string(&mode).unwrap();
172        assert_eq!(json, r#""none""#);
173    }
174
175    #[test]
176    fn test_serialize_partial_as_object() {
177        let mode = ArrayIndexMode::ITEM | ArrayIndexMode::WILDCARD;
178        let json = serde_json::to_string(&mode).unwrap();
179        // Partial combinations serialize as object
180        assert!(json.contains("\"item\":true"));
181        assert!(json.contains("\"wildcard\":true"));
182        assert!(json.contains("\"position\":false"));
183    }
184
185    #[test]
186    fn test_round_trip_none() {
187        let original = ArrayIndexMode::NONE;
188        let json = serde_json::to_string(&original).unwrap();
189        let parsed: ArrayIndexMode = serde_json::from_str(&json).unwrap();
190        assert_eq!(original, parsed);
191    }
192
193    #[test]
194    fn test_round_trip_all() {
195        let original = ArrayIndexMode::ALL;
196        let json = serde_json::to_string(&original).unwrap();
197        let parsed: ArrayIndexMode = serde_json::from_str(&json).unwrap();
198        assert_eq!(original, parsed);
199    }
200
201    #[test]
202    fn test_round_trip_partial() {
203        let original = ArrayIndexMode::ITEM | ArrayIndexMode::WILDCARD;
204        let json = serde_json::to_string(&original).unwrap();
205        let parsed: ArrayIndexMode = serde_json::from_str(&json).unwrap();
206        assert_eq!(original, parsed);
207    }
208
209    #[test]
210    fn test_deserialize_object_form() {
211        let json = r#"{"item":true,"wildcard":true,"position":false}"#;
212        let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
213        assert!(mode.has_item());
214        assert!(mode.has_wildcard());
215        assert!(!mode.has_position());
216    }
217
218    #[test]
219    fn test_deserialize_preset_all() {
220        let json = r#""all""#;
221        let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
222        assert_eq!(mode, ArrayIndexMode::ALL);
223    }
224
225    #[test]
226    fn test_deserialize_preset_none() {
227        let json = r#""none""#;
228        let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
229        assert_eq!(mode, ArrayIndexMode::NONE);
230    }
231
232    #[test]
233    fn test_deserialize_preset_case_insensitive() {
234        let cases = [
235            ("\"ALL\"", ArrayIndexMode::ALL),
236            ("\"None\"", ArrayIndexMode::NONE),
237            ("\"NONE\"", ArrayIndexMode::NONE),
238            ("\"All\"", ArrayIndexMode::ALL),
239        ];
240        for (json, expected) in cases {
241            let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
242            assert_eq!(mode, expected, "Failed for input: {}", json);
243        }
244    }
245
246    #[test]
247    fn test_deserialize_empty_object_is_none() {
248        let json = r#"{}"#;
249        let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
250        assert_eq!(mode, ArrayIndexMode::NONE);
251    }
252
253    #[test]
254    fn test_deserialize_partial_object() {
255        // Missing fields default to false
256        let json = r#"{"wildcard":true}"#;
257        let mode: ArrayIndexMode = serde_json::from_str(json).unwrap();
258        assert!(!mode.has_item());
259        assert!(mode.has_wildcard());
260        assert!(!mode.has_position());
261    }
262
263    #[test]
264    fn test_deserialize_invalid_preset_returns_error() {
265        let json = r#""invalid""#;
266        let result: Result<ArrayIndexMode, _> = serde_json::from_str(json);
267        assert!(result.is_err());
268        let err = result.unwrap_err().to_string();
269        assert!(
270            err.contains("unknown preset"),
271            "Error should mention 'unknown preset': {}",
272            err
273        );
274    }
275}