scratchstack_aspen/condition/
mod.rs

1//! Condition handling for Aspen policies.
2
3mod arn;
4mod binary;
5mod boolean;
6mod date;
7mod ipaddr;
8mod null;
9mod numeric;
10
11/// Operators for conditions.
12#[allow(non_upper_case_globals)]
13pub mod op;
14
15#[cfg(test)]
16mod op_tests;
17mod string;
18mod variant;
19
20pub use {op::ConditionOp, variant::Variant};
21
22use {
23    crate::{from_str_json, serutil::StringLikeList, AspenError, Context, PolicyVersion},
24    serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize},
25    std::{
26        borrow::Borrow,
27        collections::{
28            btree_map::{
29                Entry, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Range, RangeMut, Values, ValuesMut,
30            },
31            BTreeMap,
32        },
33        iter::{Extend, FromIterator, IntoIterator},
34        ops::{Index, RangeBounds},
35    },
36};
37
38/// A map of condition variables to their allowed values.
39pub type ConditionMap = BTreeMap<String, StringLikeList<String>>;
40
41/// Representation of an Aspen condition clause in a statement.
42///
43/// This is (logically and physically) a two-level map. The first level (this structure) maps [ConditionOp] operators
44/// to a [ConditionMap]. The second level, the [ConditionMap] itself, maps condition variable names to a list of
45/// allowed values.
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct Condition {
48    map: BTreeMap<ConditionOp, ConditionMap>,
49}
50
51from_str_json!(Condition);
52
53impl<'de> Deserialize<'de> for Condition {
54    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
55        let map = BTreeMap::deserialize(deserializer)?;
56
57        Ok(Self {
58            map,
59        })
60    }
61}
62
63impl Serialize for Condition {
64    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
65        self.map.serialize(serializer)
66    }
67}
68
69impl Condition {
70    /// Create a new condition clause with no values.
71    #[inline]
72    pub fn new() -> Self {
73        Self {
74            map: BTreeMap::new(),
75        }
76    }
77
78    /// Moves all elements from `other` into `self`, leaving `other` empty.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
84    ///
85    /// let mut a = Condition::new();
86    /// a.insert(condop::StringEquals, ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]));
87    ///
88    /// let mut b = Condition::new();
89    /// b.insert(condop::StringLike, ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]));
90    ///
91    /// a.append(&mut b);
92    ///
93    /// assert_eq!(a.len(), 2);
94    /// assert_eq!(b.len(), 0);
95    /// ```
96    #[inline]
97    pub fn append(&mut self, other: &mut Self) {
98        self.map.append(&mut other.map);
99    }
100
101    /// Clears the condition clause, removing all elements.
102    ///
103    /// # Examples
104    ///
105    /// Basic usage:
106    ///
107    /// ```
108    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
109    ///
110    /// let mut a = Condition::new();
111    /// a.insert(condop::StringEquals, ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]));
112    /// a.clear();
113    /// assert!(a.is_empty());
114    /// ```
115    #[inline]
116    pub fn clear(&mut self) {
117        self.map.clear();
118    }
119
120    /// Returns `true` if the condition clause contains a value for the specified [ConditionOp] operator.
121    ///
122    /// # Examples
123    ///
124    /// Basic usage:
125    ///
126    /// ```
127    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
128    ///
129    /// let mut condition = Condition::new();
130    /// condition.insert(condop::StringEquals, ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]));
131    /// assert_eq!(condition.contains_key(&condop::StringEquals), true);
132    /// assert_eq!(condition.contains_key(&condop::NumericEquals), false);
133    /// ```
134    #[inline]
135    pub fn contains_key<Q>(&self, key: &Q) -> bool
136    where
137        ConditionOp: Borrow<Q>,
138        Q: Ord + ?Sized,
139    {
140        self.map.contains_key(key)
141    }
142
143    /// Gets the given [ConditionOp] operator's corresponding entry in the map for in-place manipulation.
144    ///
145    /// # Examples
146    ///
147    /// Basic usage:
148    ///
149    /// ```
150    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
151    ///
152    /// let mut condition = Condition::new();
153    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
154    /// condition.insert(condop::StringEquals, cmap);
155    /// condition.entry(condop::StringEquals).and_modify(|e| {
156    ///     e.insert("b".to_string(), StringLikeList::<String>::from("B".to_string()));
157    /// });
158    ///
159    /// assert_eq!(condition.get(&condop::StringEquals).unwrap().len(), 2);
160    /// ```
161    #[inline]
162    pub fn entry(&mut self, key: ConditionOp) -> Entry<'_, ConditionOp, ConditionMap> {
163        self.map.entry(key)
164    }
165
166    /// Returns a reference to the [ConditionMap] corresponding to the [ConditionOp] operator.
167    ///
168    /// The key may be any borrowed form of the map's key type, but the ordering
169    /// on the borrowed form *must* match the ordering on the key type.
170    ///
171    /// # Examples
172    ///
173    /// Basic usage:
174    ///
175    /// ```
176    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
177    ///
178    /// let mut condition = Condition::new();
179    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
180    /// condition.insert(condop::StringEquals, cmap.clone());
181    /// assert_eq!(condition.get(&condop::StringEquals), Some(&cmap));
182    /// assert_eq!(condition.get(&condop::StringLike), None);
183    /// ```
184    #[inline]
185    pub fn get<Q>(&self, key: &Q) -> Option<&ConditionMap>
186    where
187        ConditionOp: Borrow<Q>,
188        Q: Ord + ?Sized,
189    {
190        self.map.get(key)
191    }
192
193    /// Returns the `(ConditionOp, ConditionMap)` key-value pair corresponding to the supplied
194    /// [ConditionOp] operator.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
200    ///
201    /// let mut condition = Condition::new();
202    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
203    /// condition.insert(condop::StringEquals, cmap.clone());
204    /// assert_eq!(condition.get_key_value(&condop::StringEquals), Some((&condop::StringEquals, &cmap)));
205    /// assert_eq!(condition.get_key_value(&condop::StringLike), None);
206    /// ```
207    #[inline]
208    pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&ConditionOp, &ConditionMap)>
209    where
210        ConditionOp: Borrow<Q>,
211        Q: Ord + ?Sized,
212    {
213        self.map.get_key_value(key)
214    }
215
216    /// Returns a mutable reference to the [ConditionMap] corresponding to the [ConditionOp] operator.
217    ///
218    /// # Examples
219    ///
220    /// Basic usage:
221    ///
222    /// ```
223    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
224    ///
225    /// let mut condition = Condition::new();
226    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
227    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
228    /// condition.insert(condop::StringEquals, cmap1);
229    /// if let Some(x) = condition.get_mut(&condop::StringEquals) {
230    ///     *x = cmap2.clone();
231    /// }
232    /// assert_eq!(condition[&condop::StringEquals], cmap2);
233    /// ```
234    #[inline]
235    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut ConditionMap>
236    where
237        ConditionOp: Borrow<Q>,
238        Q: Ord + ?Sized,
239    {
240        self.map.get_mut(key)
241    }
242
243    #[inline]
244    /// Inserts a key-value pair into the Condition clause.
245    ///
246    /// If the clause did not have this operator present, `None` is returned.
247    ///
248    /// If the clause did have this operator present, the value is updated, and the old
249    /// value is returned.
250    ///
251    /// # Examples
252    ///
253    /// Basic usage:
254    ///
255    /// ```
256    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
257    ///
258    /// let mut condition = Condition::new();
259    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
260    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
261    ///
262    /// assert_eq!(condition.insert(condop::StringEquals, cmap1.clone()), None);
263    /// assert_eq!(condition.insert(condop::StringEquals, cmap2.clone()), Some(cmap1));
264    /// ```
265    pub fn insert(&mut self, key: ConditionOp, value: ConditionMap) -> Option<ConditionMap> {
266        self.map.insert(key, value)
267    }
268
269    /// Creates a consuming iterator visiting all the [ConditionOp] operators, in sorted order.
270    /// The map cannot be used after calling this.
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
276    ///
277    /// let mut condition = Condition::new();
278    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
279    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
280    /// condition.insert(condop::StringEquals, cmap1);
281    /// condition.insert(condop::StringEqualsIgnoreCase, cmap2);
282    ///
283    /// let keys: Vec<ConditionOp> = condition.into_keys().collect();
284    /// assert_eq!(keys, [condop::StringEquals, condop::StringEqualsIgnoreCase]);
285    /// ```
286    #[inline]
287    pub fn into_keys(self) -> IntoKeys<ConditionOp, ConditionMap> {
288        self.map.into_keys()
289    }
290
291    /// Creates a consuming iterator visiting all the values, in order by the [ConditionOp] operator.
292    /// The map cannot be used after calling this.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
298    ///
299    /// let mut condition = Condition::new();
300    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
301    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
302    /// condition.insert(condop::StringEquals, cmap1.clone());
303    /// condition.insert(condop::StringEqualsIgnoreCase, cmap2.clone());
304    ///
305    /// let values: Vec<ConditionMap> = condition.into_values().collect();
306    /// assert_eq!(values, [cmap1, cmap2]);
307    /// ```
308    #[inline]
309    pub fn into_values(self) -> IntoValues<ConditionOp, ConditionMap> {
310        self.map.into_values()
311    }
312
313    /// Returns `true` if the condition clause contains no elements.
314    ///
315    /// # Examples
316    ///
317    /// Basic usage:
318    ///
319    /// ```
320    /// # use scratchstack_aspen::{condop, Condition, ConditionMap, StringLikeList};
321    ///
322    /// let mut condition = Condition::new();
323    /// assert!(condition.is_empty());
324    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
325    /// condition.insert(condop::StringEquals, cmap);
326    /// assert!(!condition.is_empty());
327    /// ```
328    #[inline]
329    pub fn is_empty(&self) -> bool {
330        self.map.is_empty()
331    }
332
333    /// Gets an iterator over the `(&ConditionOp, &ConditionMap)` entries of the condition clause, sorted by
334    /// [ConditionOp] operator.
335    ///
336    /// # Examples
337    ///
338    /// Basic usage:
339    ///
340    /// ```
341    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
342    ///
343    /// let mut condition = Condition::new();
344    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
345    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
346    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
347    ///
348    /// condition.insert(condop::Bool, cmap2.clone());
349    /// condition.insert(condop::ArnLike, cmap1.clone());
350    /// condition.insert(condop::StringEquals, cmap3.clone());
351    ///
352    /// let values: Vec<(&ConditionOp, &ConditionMap)> = condition.iter().collect();
353    /// assert_eq!(values, vec![(&condop::ArnLike, &cmap1), (&condop::Bool, &cmap2), (&condop::StringEquals, &cmap3)]);
354    /// ```
355    #[inline]
356    pub fn iter(&self) -> Iter<'_, ConditionOp, ConditionMap> {
357        self.map.iter()
358    }
359
360    /// Gets an mutable iterator over the `(&ConditionOp, &mut ConditionMap)` entries of the condition clause, sorted
361    /// by [ConditionOp] operator.
362    ///
363    /// # Examples
364    ///
365    /// Basic usage:
366    ///
367    /// ```
368    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
369    ///
370    /// let mut condition = Condition::new();
371    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
372    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
373    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
374    ///
375    /// condition.insert(condop::Bool, cmap2.clone());
376    /// condition.insert(condop::ArnLike, cmap1.clone());
377    /// condition.insert(condop::StringEquals, cmap3.clone());
378    ///
379    /// let values: Vec<(&ConditionOp, &ConditionMap)> = condition.iter().collect();
380    /// // Add a new variable to the Bool operator.
381    /// for (key, value) in condition.iter_mut() {
382    ///     if key == &condop::Bool {
383    ///        value.insert("d".to_string(), StringLikeList::<String>::from("D".to_string()));
384    ///     }
385    /// }
386    /// ```
387    #[inline]
388    pub fn iter_mut(&mut self) -> IterMut<'_, ConditionOp, ConditionMap> {
389        self.map.iter_mut()
390    }
391
392    /// Gets an iterator over the [ConditionOp] operator keys of the map, in sorted order.
393    ///
394    /// # Examples
395    ///
396    /// Basic usage:
397    ///
398    /// ```
399    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
400    ///
401    /// let mut condition = Condition::new();
402    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
403    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
404    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
405    ///
406    /// condition.insert(condop::Bool, cmap2.clone());
407    /// condition.insert(condop::ArnLike, cmap1.clone());
408    /// condition.insert(condop::StringEquals, cmap3.clone());
409    ///
410    /// let keys: Vec<ConditionOp> = condition.keys().cloned().collect();
411    /// assert_eq!(keys, [condop::ArnLike, condop::Bool, condop::StringEquals]);
412    /// ```
413    #[inline]
414    pub fn keys(&self) -> Keys<'_, ConditionOp, ConditionMap> {
415        self.map.keys()
416    }
417
418    /// Returns the number of elements in the condition clause.
419    ///
420    /// # Examples
421    ///
422    /// Basic usage:
423    ///
424    /// ```
425    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
426    ///
427    /// let mut condition = Condition::new();
428    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
429    /// assert_eq!(condition.len(), 0);
430    /// condition.insert(condop::StringEquals, cmap);
431    /// assert_eq!(condition.len(), 1);
432    /// ```
433    #[inline]
434    pub fn len(&self) -> usize {
435        self.map.len()
436    }
437
438    /// Constructs a double-ended iterator over a sub-range of elements in the condition clause.
439    /// The simplest way is to use the range syntax `min..max`, thus `range(min..max)` will
440    /// yield elements from min (inclusive) to max (exclusive).
441    /// The range may also be entered as `(Bound<T>, Bound<T>)`, so for example
442    /// `range((Excluded(condop::ArnLike), Included(condop::StringEquals)))` will yield a
443    /// left-exclusive, right-inclusive range.
444    ///
445    /// # Panics
446    ///
447    /// Panics if range `start > end`.
448    /// Panics if range `start == end` and both bounds are `Excluded`.
449    ///
450    /// # Examples
451    ///
452    /// Basic usage:
453    ///
454    /// ```
455    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
456    /// use std::ops::Bound::Included;
457    ///
458    /// let mut condition = Condition::new();
459    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
460    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
461    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
462    ///
463    /// condition.insert(condop::ArnLike, cmap1.clone());
464    /// condition.insert(condop::Bool, cmap2.clone());
465    /// condition.insert(condop::StringEquals, cmap3.clone());
466    ///
467    /// let result: Vec<(&ConditionOp, &ConditionMap)> = condition.range((Included(condop::Bool), Included(condop::NumericEquals))).collect();
468    /// assert_eq!(result, vec![(&condop::Bool, &cmap2)]);
469    /// ```
470    #[inline]
471    pub fn range<T, R>(&self, range: R) -> Range<'_, ConditionOp, ConditionMap>
472    where
473        ConditionOp: Borrow<T>,
474        T: Ord + ?Sized,
475        R: RangeBounds<T>,
476    {
477        self.map.range(range)
478    }
479
480    /// Constructs a mutable double-ended iterator over a sub-range of elements in the condition clause.
481    /// The simplest way is to use the range syntax `min..max`, thus `range(min..max)` will
482    /// yield elements from min (inclusive) to max (exclusive).
483    /// The range may also be entered as `(Bound<T>, Bound<T>)`, so for example
484    /// `range((Excluded(condop::ArnLike), Included(condop::StringEquals)))` will yield a
485    /// left-exclusive, right-inclusive range.
486    ///
487    /// # Panics
488    ///
489    /// Panics if range `start > end`.
490    /// Panics if range `start == end` and both bounds are `Excluded`.
491    ///
492    /// # Examples
493    ///
494    /// Basic usage:
495    ///
496    /// ```
497    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
498    /// use std::ops::Bound::Included;
499    ///
500    /// let mut condition = Condition::new();
501    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
502    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
503    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
504    ///
505    /// condition.insert(condop::ArnLike, cmap1);
506    /// condition.insert(condop::Bool, cmap2);
507    /// condition.insert(condop::StringEquals, cmap3);
508    ///
509    /// for (_, cmap) in condition.range_mut((Included(condop::Bool), Included(condop::NumericEquals))) {
510    ///     cmap.insert("d".to_string(), StringLikeList::<String>::from("D".to_string()));
511    /// }
512    ///
513    /// assert_eq!(condition.get(&condop::Bool).unwrap().len(), 2);
514    /// ```
515    #[inline]
516    pub fn range_mut<T, R>(&mut self, range: R) -> RangeMut<'_, ConditionOp, ConditionMap>
517    where
518        ConditionOp: Borrow<T>,
519        T: Ord + ?Sized,
520        R: RangeBounds<T>,
521    {
522        self.map.range_mut(range)
523    }
524
525    /// Removes a [ConditionOp] operator from the condition clause, returning the [ConditionMap] corresponding to the
526    /// operator if the operator was previously in the clause.
527    ///
528    /// # Examples
529    ///
530    /// Basic usage:
531    ///
532    /// ```
533    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
534    ///
535    /// let mut condition = Condition::new();
536    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
537    ///
538    /// condition.insert(condop::Bool, cmap.clone());
539    ///
540    /// assert_eq!(condition.remove(&condop::Bool), Some(cmap));
541    /// assert_eq!(condition.remove(&condop::Bool), None);
542    /// ```
543    #[inline]
544    pub fn remove<Q>(&mut self, key: &Q) -> Option<ConditionMap>
545    where
546        ConditionOp: Borrow<Q>,
547        Q: Ord + ?Sized,
548    {
549        self.map.remove(key)
550    }
551
552    /// Removes a [ConditionOp] operator from the condition clause, returning the stored operator and [ConditionMap]
553    /// if the operator was previously in the clause.
554    ///
555    /// # Examples
556    ///
557    /// Basic usage:
558    ///
559    /// ```
560    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
561    ///
562    /// let mut condition = Condition::new();
563    /// let cmap = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
564    ///
565    /// condition.insert(condop::Bool, cmap.clone());
566    ///
567    /// assert_eq!(condition.remove_entry(&condop::Bool), Some((condop::Bool, cmap)));
568    /// assert_eq!(condition.remove(&condop::Bool), None);
569    /// ```
570
571    #[inline]
572    pub fn remove_entry<Q>(&mut self, key: &Q) -> Option<(ConditionOp, ConditionMap)>
573    where
574        ConditionOp: Borrow<Q>,
575        Q: Ord + ?Sized,
576    {
577        self.map.remove_entry(key)
578    }
579
580    /// Retains only the elements specified by the predicate.
581    ///
582    /// In other words, remove all pairs `(cond_op, cond_map)` for which `f(&cond_op, &mut cond_map)` returns `false`.
583    /// The elements are visited in ascending [ConditionOp] order.
584    ///
585    /// # Examples
586    ///
587    /// ```
588    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
589    ///
590    /// let mut condition = Condition::new();
591    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
592    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
593    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
594    ///
595    /// condition.insert(condop::ArnLike, cmap1);
596    /// condition.insert(condop::Bool, cmap2.clone());
597    /// condition.insert(condop::StringEquals, cmap3);
598    ///
599    /// // Keep only the Bool key.
600    /// condition.retain(|&k, _| k == condop::Bool);
601    /// assert!(condition.into_iter().eq(vec![(condop::Bool, cmap2)]));
602    /// ```
603    #[inline]
604    pub fn retain<F>(&mut self, f: F)
605    where
606        F: FnMut(&ConditionOp, &mut ConditionMap) -> bool,
607    {
608        self.map.retain(f)
609    }
610
611    /// Splits the collection into two at the given [ConditionOp] operator. Returns everything on and after the given
612    /// [ConditionOp].
613    ///
614    /// # Examples
615    ///
616    /// ```
617    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
618    ///
619    /// let mut a = Condition::new();
620    /// let cmap = ConditionMap::new();
621    ///
622    /// a.insert(condop::ArnLike, cmap.clone());
623    /// a.insert(condop::Bool, cmap.clone());
624    /// a.insert(condop::DateEquals, cmap.clone());
625    /// a.insert(condop::NumericEquals, cmap.clone());
626    /// a.insert(condop::StringEquals, cmap.clone());
627    ///
628    /// let b= a.split_off(&condop::DateEquals);
629    /// assert_eq!(a.len(), 2);
630    /// assert_eq!(b.len(), 3);
631    ///
632    /// assert_eq!(a.into_keys().collect::<Vec<_>>(), vec![condop::ArnLike, condop::Bool]);
633    /// assert_eq!(b.into_keys().collect::<Vec<_>>(), vec![condop::DateEquals, condop::NumericEquals, condop::StringEquals]);
634    /// ```
635
636    #[inline]
637    pub fn split_off<Q>(&mut self, key: &Q) -> Condition
638    where
639        ConditionOp: Borrow<Q>,
640        Q: Ord + ?Sized,
641    {
642        Condition {
643            map: self.map.split_off(key),
644        }
645    }
646
647    /// Gets an iterator over the [ConditionMap] values of the map, in order by [ConditionOp] key.
648    ///
649    /// # Examples
650    ///
651    /// Basic usage:
652    ///
653    /// ```
654    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
655    ///
656    /// let mut condition = Condition::new();
657    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
658    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
659    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
660    ///
661    /// condition.insert(condop::Bool, cmap2.clone());
662    /// condition.insert(condop::ArnLike, cmap1.clone());
663    /// condition.insert(condop::StringEquals, cmap3.clone());
664    ///
665    /// let values: Vec<ConditionMap> = condition.values().cloned().collect();
666    /// assert_eq!(values, [cmap1, cmap2, cmap3]);
667    /// ```
668    #[inline]
669    pub fn values(&self) -> Values<'_, ConditionOp, ConditionMap> {
670        self.map.values()
671    }
672
673    /// Gets an iterator over the mutable [ConditionMap] values of the map, in order by [ConditionOp] key.
674    ///
675    /// # Examples
676    ///
677    /// Basic usage:
678    ///
679    /// ```
680    /// # use scratchstack_aspen::{condop, Condition, ConditionOp, ConditionMap, StringLikeList};
681    ///
682    /// let mut condition = Condition::new();
683    /// let cmap1 = ConditionMap::from_iter(vec![("a".to_string(), StringLikeList::<String>::from("A".to_string()))]);
684    /// let cmap2 = ConditionMap::from_iter(vec![("b".to_string(), StringLikeList::<String>::from("B".to_string()))]);
685    /// let cmap3 = ConditionMap::from_iter(vec![("c".to_string(), StringLikeList::<String>::from("C".to_string()))]);
686    ///
687    /// condition.insert(condop::Bool, cmap2);
688    /// condition.insert(condop::ArnLike, cmap1);
689    /// condition.insert(condop::StringEquals, cmap3);
690    ///
691    /// for value in condition.values_mut() {
692    ///    value.insert("d".to_string(), StringLikeList::<String>::from("D".to_string()));
693    /// }
694    ///
695    /// assert_eq!(condition.get(&condop::ArnLike).unwrap().len(), 2);
696    /// ```
697    #[inline]
698    pub fn values_mut(&mut self) -> ValuesMut<'_, ConditionOp, ConditionMap> {
699        self.map.values_mut()
700    }
701
702    /// Indicates whether this condition clause matches the request [Context]. This condition is interpreted using the
703    /// specified [PolicyVersion].
704    ///
705    /// # Errors
706    ///
707    /// If a condition clause contains a malformed variable, [AspenError::InvalidSubstitution] is returned.
708    pub fn matches(&self, context: &Context, pv: PolicyVersion) -> Result<bool, AspenError> {
709        for (op, map) in self.iter() {
710            if !op.matches(map, context, pv)? {
711                return Ok(false);
712            }
713        }
714
715        Ok(true)
716    }
717}
718
719impl Default for Condition {
720    #[inline]
721    fn default() -> Self {
722        Self::new()
723    }
724}
725
726impl Extend<(ConditionOp, ConditionMap)> for Condition {
727    #[inline]
728    fn extend<I>(&mut self, iter: I)
729    where
730        I: IntoIterator<Item = (ConditionOp, ConditionMap)>,
731    {
732        self.map.extend(iter)
733    }
734}
735
736impl<const N: usize> From<[(ConditionOp, ConditionMap); N]> for Condition {
737    #[inline]
738    fn from(array: [(ConditionOp, ConditionMap); N]) -> Self {
739        Condition {
740            map: BTreeMap::from(array),
741        }
742    }
743}
744
745impl FromIterator<(ConditionOp, ConditionMap)> for Condition {
746    #[inline]
747    fn from_iter<I>(iter: I) -> Self
748    where
749        I: IntoIterator<Item = (ConditionOp, ConditionMap)>,
750    {
751        Condition {
752            map: BTreeMap::from_iter(iter),
753        }
754    }
755}
756
757impl<Q> Index<&Q> for Condition
758where
759    ConditionOp: Borrow<Q>,
760    Q: Ord + ?Sized,
761{
762    type Output = ConditionMap;
763
764    fn index(&self, key: &Q) -> &ConditionMap {
765        self.map.index(key)
766    }
767}
768
769impl<'a> IntoIterator for &'a Condition {
770    type Item = (&'a ConditionOp, &'a ConditionMap);
771    type IntoIter = Iter<'a, ConditionOp, ConditionMap>;
772    fn into_iter(self) -> Iter<'a, ConditionOp, ConditionMap> {
773        self.map.iter()
774    }
775}
776
777impl<'a> IntoIterator for &'a mut Condition {
778    type Item = (&'a ConditionOp, &'a mut ConditionMap);
779    type IntoIter = IterMut<'a, ConditionOp, ConditionMap>;
780    fn into_iter(self) -> IterMut<'a, ConditionOp, ConditionMap> {
781        self.map.iter_mut()
782    }
783}
784
785impl IntoIterator for Condition {
786    type Item = (ConditionOp, ConditionMap);
787    type IntoIter = IntoIter<ConditionOp, ConditionMap>;
788    fn into_iter(self) -> IntoIter<ConditionOp, ConditionMap> {
789        self.map.into_iter()
790    }
791}
792
793#[cfg(test)]
794mod test {
795    use {
796        crate::{condop, serutil::StringLikeList, Condition, ConditionMap, ConditionOp},
797        pretty_assertions::assert_eq,
798    };
799
800    #[test_log::test]
801    fn test_map_ops() {
802        let mut c1 = Condition::default();
803        let mut c2 = Condition::new();
804
805        assert_eq!(c1, c2);
806
807        let mut cmap1 = ConditionMap::default();
808        cmap1.insert("a".to_string(), StringLikeList::from(vec!["A".to_string()]));
809        cmap1.insert("b".to_string(), StringLikeList::from(vec!["B".to_string()]));
810
811        let mut cmap2 = ConditionMap::default();
812        cmap2.insert("c".to_string(), StringLikeList::from(vec!["C".to_string()]));
813
814        let mut cmap3 = ConditionMap::default();
815        cmap3.insert("d".to_string(), StringLikeList::from(vec!["D".to_string()]));
816
817        c2.insert(condop::StringEquals, cmap1.clone());
818        c2.insert(condop::StringEqualsIgnoreCase, cmap3.clone());
819        assert_eq!(c1.len(), 0);
820        assert!(c1.is_empty());
821        assert_eq!(c2.len(), 2);
822        assert!(!c2.is_empty());
823
824        assert_eq!(c2[&condop::StringEquals], cmap1);
825
826        assert!(c2.contains_key(&condop::StringEquals));
827        assert!(!c1.contains_key(&condop::StringEquals));
828        assert_eq!(c1.get_key_value(&condop::StringEquals), None);
829        assert_eq!(c2.get_key_value(&condop::StringEquals), Some((&condop::StringEquals, &cmap1)));
830
831        // Make this look like cmap2.
832        let c2_map = c2.get_mut(&condop::StringEquals).unwrap();
833        c2_map.insert("c".to_string(), StringLikeList::from(vec!["C".to_string()]));
834        c2_map.remove("a");
835        c2_map.remove("b");
836        assert_eq!(c2_map, &cmap2);
837
838        c1.append(&mut c2);
839        assert_eq!(c1.len(), 2);
840        assert_eq!(c2.len(), 0);
841        assert!(!c1.is_empty());
842        assert!(c2.is_empty());
843        assert!(c1.contains_key(&condop::StringEquals));
844        assert_eq!(c1[&condop::StringEquals], cmap2);
845        c1.retain(|k, _| k == &condop::StringEquals);
846        assert_eq!(c1.keys().collect::<Vec<_>>(), vec![&condop::StringEquals]);
847        assert_eq!(c1.values().collect::<Vec<_>>(), vec![&cmap2]);
848
849        c1.clear();
850        assert_eq!(c1.len(), 0);
851        assert!(c1.is_empty());
852        assert!(!c1.contains_key(&condop::StringEquals));
853
854        let c1_array: [(ConditionOp, ConditionMap); 4] = [
855            (condop::DateEquals, cmap1.clone()),
856            (condop::NumericEquals, cmap1.clone()),
857            (condop::StringEquals, cmap2.clone()),
858            (condop::StringEqualsIgnoreCase, cmap3.clone()),
859        ];
860
861        let mut c1 = Condition::from(c1_array);
862        c1.entry(condop::NumericEquals).and_modify(|v| {
863            v.remove("a");
864            v.remove("b");
865            v.insert("c".to_string(), StringLikeList::from(vec!["C".to_string()]));
866        });
867        assert_eq!(c1[&condop::NumericEquals], cmap2);
868        assert!(c1.remove(&condop::Bool).is_none());
869        assert!(c1.remove_entry(&condop::Bool).is_none());
870        assert_eq!(c1.remove_entry(&condop::NumericEquals), Some((condop::NumericEquals, cmap2.clone())));
871        c1.insert(condop::NumericEquals, cmap1.clone());
872
873        c1.range_mut(condop::Bool..condop::NumericEquals).for_each(|(k, v)| {
874            assert_eq!(k, &condop::DateEquals);
875            assert_eq!(v, &cmap1);
876
877            // Make this look like cmap2
878            v.remove("a");
879            v.remove("b");
880            v.insert("c".to_string(), StringLikeList::from(vec!["C".to_string()]));
881        });
882
883        c1.range(condop::Bool..condop::NumericEquals).for_each(|(k, v)| {
884            assert_eq!(k, &condop::DateEquals);
885            assert_eq!(v, &cmap2);
886        });
887
888        let c2 = c1.split_off(&condop::StringEquals);
889        let c1_vec = (&mut c1).into_iter().collect::<Vec<_>>();
890        assert_eq!(c1_vec, vec![(&condop::DateEquals, &mut cmap2), (&condop::NumericEquals, &mut cmap1)]);
891        let c1_vec = (&c1).into_iter().collect::<Vec<_>>();
892        assert_eq!(c1_vec, vec![(&condop::DateEquals, &cmap2), (&condop::NumericEquals, &cmap1)]);
893        let c2 = c2.into_iter().collect::<Vec<_>>();
894        assert_eq!(c2, vec![(condop::StringEquals, cmap2.clone()), (condop::StringEqualsIgnoreCase, cmap3.clone())]);
895
896        assert_eq!(c1.clone().into_keys().collect::<Vec<_>>(), vec![condop::DateEquals, condop::NumericEquals]);
897        assert_eq!(c1.clone().into_values().collect::<Vec<_>>(), vec![cmap2.clone(), cmap1.clone()]);
898
899        c1.extend([(condop::StringEquals, cmap2.clone()), (condop::StringEqualsIgnoreCase, cmap3.clone())]);
900        c1.values_mut().for_each(|v| {
901            if v == &cmap3 {
902                // Make this look like cmap2.
903                v.remove("d");
904                v.insert("c".to_string(), StringLikeList::from(vec!["C".to_string()]));
905            }
906        });
907        assert_eq!(c1[&condop::StringEqualsIgnoreCase], cmap2);
908
909        c1.iter_mut().for_each(|(k, v)| {
910            if k == &condop::NumericEquals {
911                // Make this look like cmap3.
912                v.clear();
913                v.insert("d".to_string(), StringLikeList::from(vec!["D".to_string()]));
914            }
915        });
916        assert_eq!(c1[&condop::NumericEquals], cmap3);
917
918        let c2 = Condition::from_iter(c1.iter().map(|(k, v)| (*k, v.clone())));
919        assert_eq!(c1, c2);
920    }
921}