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}