Skip to main content

iam_rs/core/
operator.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
4#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
5pub enum OperatorType {
6    String,
7    Numeric,
8    Date,
9    Boolean,
10    Binary,
11    IpAddress,
12    Arn,
13    Null,
14}
15
16/// Represents the different types of condition operators available in IAM policies
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
18#[serde(rename_all = "PascalCase")]
19#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
20pub enum IAMOperator {
21    // String condition operators
22    #[serde(rename = "StringEquals")]
23    StringEquals,
24    #[serde(rename = "StringNotEquals")]
25    StringNotEquals,
26    #[serde(rename = "StringEqualsIgnoreCase")]
27    StringEqualsIgnoreCase,
28    #[serde(rename = "StringNotEqualsIgnoreCase")]
29    StringNotEqualsIgnoreCase,
30    #[serde(rename = "StringLike")]
31    StringLike,
32    #[serde(rename = "StringNotLike")]
33    StringNotLike,
34
35    // Multivalued string condition operators
36    #[serde(rename = "ForAllValues:StringEquals")]
37    ForAllValuesStringEquals,
38    #[serde(rename = "ForAllValues:StringEqualsIgnoreCase")]
39    ForAllValuesStringEqualsIgnoreCase,
40    #[serde(rename = "ForAnyValue:StringEquals")]
41    ForAnyValueStringEquals,
42    #[serde(rename = "ForAnyValue:StringEqualsIgnoreCase")]
43    ForAnyValueStringEqualsIgnoreCase,
44    #[serde(rename = "ForAllValues:StringNotEquals")]
45    ForAllValuesStringNotEquals,
46    #[serde(rename = "ForAllValues:StringNotEqualsIgnoreCase")]
47    ForAllValuesStringNotEqualsIgnoreCase,
48    #[serde(rename = "ForAnyValue:StringNotEquals")]
49    ForAnyValueStringNotEquals,
50    #[serde(rename = "ForAnyValue:StringNotEqualsIgnoreCase")]
51    ForAnyValueStringNotEqualsIgnoreCase,
52    #[serde(rename = "ForAllValues:StringLike")]
53    ForAllValuesStringLike,
54    #[serde(rename = "ForAnyValue:StringLike")]
55    ForAnyValueStringLike,
56    #[serde(rename = "ForAllValues:StringNotLike")]
57    ForAllValuesStringNotLike,
58    #[serde(rename = "ForAnyValue:StringNotLike")]
59    ForAnyValueStringNotLike,
60
61    // Numeric condition operators
62    #[serde(rename = "NumericEquals")]
63    NumericEquals,
64    #[serde(rename = "NumericNotEquals")]
65    NumericNotEquals,
66    #[serde(rename = "NumericLessThan")]
67    NumericLessThan,
68    #[serde(rename = "NumericLessThanEquals")]
69    NumericLessThanEquals,
70    #[serde(rename = "NumericGreaterThan")]
71    NumericGreaterThan,
72    #[serde(rename = "NumericGreaterThanEquals")]
73    NumericGreaterThanEquals,
74
75    // Date condition operators
76    #[serde(rename = "DateEquals")]
77    DateEquals,
78    #[serde(rename = "DateNotEquals")]
79    DateNotEquals,
80    #[serde(rename = "DateLessThan")]
81    DateLessThan,
82    #[serde(rename = "DateLessThanEquals")]
83    DateLessThanEquals,
84    #[serde(rename = "DateGreaterThan")]
85    DateGreaterThan,
86    #[serde(rename = "DateGreaterThanEquals")]
87    DateGreaterThanEquals,
88
89    // Boolean condition operators
90    #[serde(rename = "Bool")]
91    Bool,
92    #[serde(rename = "ForAllValues:Bool")]
93    ForAllValuesBool,
94    #[serde(rename = "ForAnyValue:Bool")]
95    ForAnyValueBool,
96
97    // Binary condition operators
98    #[serde(rename = "BinaryEquals")]
99    BinaryEquals,
100
101    // IP address condition operators
102    #[serde(rename = "IpAddress")]
103    IpAddress,
104    #[serde(rename = "NotIpAddress")]
105    NotIpAddress,
106
107    // ARN condition operators
108    #[serde(rename = "ArnEquals")]
109    ArnEquals,
110    #[serde(rename = "ArnLike")]
111    ArnLike,
112    #[serde(rename = "ArnNotEquals")]
113    ArnNotEquals,
114    #[serde(rename = "ArnNotLike")]
115    ArnNotLike,
116
117    // Multivalued ARN condition operators
118    #[serde(rename = "ForAllValues:ArnEquals")]
119    ForAllValuesArnEquals,
120    #[serde(rename = "ForAllValues:ArnLike")]
121    ForAllValuesArnLike,
122    #[serde(rename = "ForAnyValue:ArnEquals")]
123    ForAnyValueArnEquals,
124    #[serde(rename = "ForAnyValue:ArnLike")]
125    ForAnyValueArnLike,
126    #[serde(rename = "ForAllValues:ArnNotEquals")]
127    ForAllValuesArnNotEquals,
128    #[serde(rename = "ForAllValues:ArnNotLike")]
129    ForAllValuesArnNotLike,
130    #[serde(rename = "ForAnyValue:ArnNotEquals")]
131    ForAnyValueArnNotEquals,
132    #[serde(rename = "ForAnyValue:ArnNotLike")]
133    ForAnyValueArnNotLike,
134
135    // Null condition operator
136    #[serde(rename = "Null")]
137    Null,
138
139    // IfExists variants (can be applied to most operators)
140    #[serde(rename = "StringEqualsIfExists")]
141    StringEqualsIfExists,
142    #[serde(rename = "StringNotEqualsIfExists")]
143    StringNotEqualsIfExists,
144    #[serde(rename = "StringEqualsIgnoreCaseIfExists")]
145    StringEqualsIgnoreCaseIfExists,
146    #[serde(rename = "StringNotEqualsIgnoreCaseIfExists")]
147    StringNotEqualsIgnoreCaseIfExists,
148    #[serde(rename = "StringLikeIfExists")]
149    StringLikeIfExists,
150    #[serde(rename = "StringNotLikeIfExists")]
151    StringNotLikeIfExists,
152    #[serde(rename = "NumericEqualsIfExists")]
153    NumericEqualsIfExists,
154    #[serde(rename = "NumericNotEqualsIfExists")]
155    NumericNotEqualsIfExists,
156    #[serde(rename = "NumericLessThanIfExists")]
157    NumericLessThanIfExists,
158    #[serde(rename = "NumericLessThanEqualsIfExists")]
159    NumericLessThanEqualsIfExists,
160    #[serde(rename = "NumericGreaterThanIfExists")]
161    NumericGreaterThanIfExists,
162    #[serde(rename = "NumericGreaterThanEqualsIfExists")]
163    NumericGreaterThanEqualsIfExists,
164    #[serde(rename = "DateEqualsIfExists")]
165    DateEqualsIfExists,
166    #[serde(rename = "DateNotEqualsIfExists")]
167    DateNotEqualsIfExists,
168    #[serde(rename = "DateLessThanIfExists")]
169    DateLessThanIfExists,
170    #[serde(rename = "DateLessThanEqualsIfExists")]
171    DateLessThanEqualsIfExists,
172    #[serde(rename = "DateGreaterThanIfExists")]
173    DateGreaterThanIfExists,
174    #[serde(rename = "DateGreaterThanEqualsIfExists")]
175    DateGreaterThanEqualsIfExists,
176    #[serde(rename = "BoolIfExists")]
177    BoolIfExists,
178    #[serde(rename = "BinaryEqualsIfExists")]
179    BinaryEqualsIfExists,
180    #[serde(rename = "IpAddressIfExists")]
181    IpAddressIfExists,
182    #[serde(rename = "NotIpAddressIfExists")]
183    NotIpAddressIfExists,
184    #[serde(rename = "ArnEqualsIfExists")]
185    ArnEqualsIfExists,
186    #[serde(rename = "ArnLikeIfExists")]
187    ArnLikeIfExists,
188    #[serde(rename = "ArnNotEqualsIfExists")]
189    ArnNotEqualsIfExists,
190    #[serde(rename = "ArnNotLikeIfExists")]
191    ArnNotLikeIfExists,
192}
193
194impl IAMOperator {
195    /// Returns true if this operator is a string-based operator
196    #[must_use]
197    pub fn is_string_operator(&self) -> bool {
198        matches!(
199            self,
200            IAMOperator::StringEquals
201                | IAMOperator::StringNotEquals
202                | IAMOperator::StringEqualsIgnoreCase
203                | IAMOperator::StringNotEqualsIgnoreCase
204                | IAMOperator::StringLike
205                | IAMOperator::StringNotLike
206                | IAMOperator::ForAllValuesStringEquals
207                | IAMOperator::ForAllValuesStringEqualsIgnoreCase
208                | IAMOperator::ForAnyValueStringEquals
209                | IAMOperator::ForAnyValueStringEqualsIgnoreCase
210                | IAMOperator::ForAllValuesStringNotEquals
211                | IAMOperator::ForAllValuesStringNotEqualsIgnoreCase
212                | IAMOperator::ForAnyValueStringNotEquals
213                | IAMOperator::ForAnyValueStringNotEqualsIgnoreCase
214                | IAMOperator::ForAllValuesStringLike
215                | IAMOperator::ForAnyValueStringLike
216                | IAMOperator::ForAllValuesStringNotLike
217                | IAMOperator::ForAnyValueStringNotLike
218                | IAMOperator::StringEqualsIfExists
219                | IAMOperator::StringNotEqualsIfExists
220                | IAMOperator::StringEqualsIgnoreCaseIfExists
221                | IAMOperator::StringNotEqualsIgnoreCaseIfExists
222                | IAMOperator::StringLikeIfExists
223                | IAMOperator::StringNotLikeIfExists
224        )
225    }
226
227    /// Returns true if this operator is a numeric-based operator
228    #[must_use]
229    pub fn is_numeric_operator(&self) -> bool {
230        matches!(
231            self,
232            IAMOperator::NumericEquals
233                | IAMOperator::NumericNotEquals
234                | IAMOperator::NumericLessThan
235                | IAMOperator::NumericLessThanEquals
236                | IAMOperator::NumericGreaterThan
237                | IAMOperator::NumericGreaterThanEquals
238                | IAMOperator::NumericEqualsIfExists
239                | IAMOperator::NumericNotEqualsIfExists
240                | IAMOperator::NumericLessThanIfExists
241                | IAMOperator::NumericLessThanEqualsIfExists
242                | IAMOperator::NumericGreaterThanIfExists
243                | IAMOperator::NumericGreaterThanEqualsIfExists
244        )
245    }
246
247    /// Returns true if this operator is a date-based operator
248    #[must_use]
249    pub fn is_date_operator(&self) -> bool {
250        matches!(
251            self,
252            IAMOperator::DateEquals
253                | IAMOperator::DateNotEquals
254                | IAMOperator::DateLessThan
255                | IAMOperator::DateLessThanEquals
256                | IAMOperator::DateGreaterThan
257                | IAMOperator::DateGreaterThanEquals
258                | IAMOperator::DateEqualsIfExists
259                | IAMOperator::DateNotEqualsIfExists
260                | IAMOperator::DateLessThanIfExists
261                | IAMOperator::DateLessThanEqualsIfExists
262                | IAMOperator::DateGreaterThanIfExists
263                | IAMOperator::DateGreaterThanEqualsIfExists
264        )
265    }
266
267    /// Returns true if this operator is a boolean-based operator
268    #[must_use]
269    pub fn is_boolean_operator(&self) -> bool {
270        matches!(
271            self,
272            IAMOperator::Bool
273                | IAMOperator::ForAllValuesBool
274                | IAMOperator::ForAnyValueBool
275                | IAMOperator::BoolIfExists
276        )
277    }
278
279    /// Returns true if this operator is an ARN-based operator
280    #[must_use]
281    pub fn is_arn_operator(&self) -> bool {
282        matches!(
283            self,
284            IAMOperator::ArnEquals
285                | IAMOperator::ArnLike
286                | IAMOperator::ArnNotEquals
287                | IAMOperator::ArnNotLike
288                | IAMOperator::ForAllValuesArnEquals
289                | IAMOperator::ForAllValuesArnLike
290                | IAMOperator::ForAnyValueArnEquals
291                | IAMOperator::ForAnyValueArnLike
292                | IAMOperator::ForAllValuesArnNotEquals
293                | IAMOperator::ForAllValuesArnNotLike
294                | IAMOperator::ForAnyValueArnNotEquals
295                | IAMOperator::ForAnyValueArnNotLike
296                | IAMOperator::ArnEqualsIfExists
297                | IAMOperator::ArnLikeIfExists
298                | IAMOperator::ArnNotEqualsIfExists
299                | IAMOperator::ArnNotLikeIfExists
300        )
301    }
302
303    /// Returns true if this operator is an IP address-based operator
304    #[must_use]
305    pub fn is_ip_operator(&self) -> bool {
306        matches!(
307            self,
308            IAMOperator::IpAddress
309                | IAMOperator::NotIpAddress
310                | IAMOperator::IpAddressIfExists
311                | IAMOperator::NotIpAddressIfExists
312        )
313    }
314
315    /// Returns true if this operator is a binary-based operator
316    #[must_use]
317    pub fn is_binary_operator(&self) -> bool {
318        matches!(
319            self,
320            IAMOperator::BinaryEquals | IAMOperator::BinaryEqualsIfExists
321        )
322    }
323
324    /// Returns true if this operator supports wildcards
325    #[must_use]
326    pub fn supports_wildcards(&self) -> bool {
327        matches!(
328            self,
329            IAMOperator::StringLike
330                | IAMOperator::StringNotLike
331                | IAMOperator::ForAllValuesStringLike
332                | IAMOperator::ForAnyValueStringLike
333                | IAMOperator::ForAllValuesStringNotLike
334                | IAMOperator::ForAnyValueStringNotLike
335                | IAMOperator::StringLikeIfExists
336                | IAMOperator::StringNotLikeIfExists
337                | IAMOperator::ArnEquals
338                | IAMOperator::ArnLike
339                | IAMOperator::ArnNotEquals
340                | IAMOperator::ArnNotLike
341                | IAMOperator::ForAllValuesArnEquals
342                | IAMOperator::ForAllValuesArnLike
343                | IAMOperator::ForAnyValueArnEquals
344                | IAMOperator::ForAnyValueArnLike
345                | IAMOperator::ForAllValuesArnNotEquals
346                | IAMOperator::ForAllValuesArnNotLike
347                | IAMOperator::ForAnyValueArnNotEquals
348                | IAMOperator::ForAnyValueArnNotLike
349                | IAMOperator::ArnEqualsIfExists
350                | IAMOperator::ArnLikeIfExists
351                | IAMOperator::ArnNotEqualsIfExists
352                | IAMOperator::ArnNotLikeIfExists
353        )
354    }
355
356    /// Returns true if this operator supports policy variables
357    ///
358    /// AWS allows policy variables only with the string and ARN condition
359    /// operators (see "IAM policy elements: Variables and tags").
360    #[must_use]
361    pub fn supports_policy_variables(&self) -> bool {
362        self.is_string_operator() || self.is_arn_operator()
363    }
364
365    /// Returns true if this operator is a multivalued operator (ForAllValues/ForAnyValue)
366    #[must_use]
367    pub fn is_multivalued_operator(&self) -> bool {
368        self.to_string().starts_with("ForAllValues:")
369            || self.to_string().starts_with("ForAnyValue:")
370    }
371
372    /// Returns true if this operator is an "`IfExists`" variant
373    #[must_use]
374    pub fn is_if_exists_operator(&self) -> bool {
375        self.to_string().ends_with("IfExists")
376    }
377
378    /// Returns true if this operator is a negated operator (Not*)
379    #[must_use]
380    pub fn is_negated_operator(&self) -> bool {
381        matches!(
382            self,
383            IAMOperator::StringNotEquals
384                | IAMOperator::StringNotEqualsIgnoreCase
385                | IAMOperator::StringNotLike
386                | IAMOperator::ForAllValuesStringNotEquals
387                | IAMOperator::ForAllValuesStringNotEqualsIgnoreCase
388                | IAMOperator::ForAnyValueStringNotEquals
389                | IAMOperator::ForAnyValueStringNotEqualsIgnoreCase
390                | IAMOperator::ForAllValuesStringNotLike
391                | IAMOperator::ForAnyValueStringNotLike
392                | IAMOperator::NumericNotEquals
393                | IAMOperator::DateNotEquals
394                | IAMOperator::NotIpAddress
395                | IAMOperator::ArnNotEquals
396                | IAMOperator::ArnNotLike
397                | IAMOperator::ForAllValuesArnNotEquals
398                | IAMOperator::ForAllValuesArnNotLike
399                | IAMOperator::ForAnyValueArnNotEquals
400                | IAMOperator::ForAnyValueArnNotLike
401                | IAMOperator::StringNotEqualsIfExists
402                | IAMOperator::StringNotEqualsIgnoreCaseIfExists
403                | IAMOperator::StringNotLikeIfExists
404                | IAMOperator::NumericNotEqualsIfExists
405                | IAMOperator::DateNotEqualsIfExists
406                | IAMOperator::NotIpAddressIfExists
407                | IAMOperator::ArnNotEqualsIfExists
408                | IAMOperator::ArnNotLikeIfExists
409        )
410    }
411
412    /// Returns true if this operator supports multiple values (arrays)
413    /// Most operators in AWS IAM can accept arrays, not just ForAllValues/ForAnyValue
414    #[must_use]
415    pub fn supports_multiple_values(&self) -> bool {
416        // Most operators support multiple values except for these specific ones
417        !matches!(
418            self,
419            IAMOperator::Null | IAMOperator::Bool | IAMOperator::BoolIfExists
420        )
421    }
422
423    /// Returns the operator category as a string
424    /// Determine the category of this operator
425    ///
426    /// This is a total function: the `match` is exhaustive over the closed
427    /// `IAMOperator` enum, so every operator maps to exactly one `OperatorType`.
428    /// A panic is impossible; the compiler enforces completeness when new
429    /// operators are added.
430    #[allow(clippy::too_many_lines)]
431    #[must_use]
432    pub fn category(&self) -> OperatorType {
433        match self {
434            // String operators
435            IAMOperator::StringEquals
436            | IAMOperator::StringNotEquals
437            | IAMOperator::StringEqualsIgnoreCase
438            | IAMOperator::StringNotEqualsIgnoreCase
439            | IAMOperator::StringLike
440            | IAMOperator::StringNotLike
441            | IAMOperator::ForAllValuesStringEquals
442            | IAMOperator::ForAllValuesStringEqualsIgnoreCase
443            | IAMOperator::ForAnyValueStringEquals
444            | IAMOperator::ForAnyValueStringEqualsIgnoreCase
445            | IAMOperator::ForAllValuesStringNotEquals
446            | IAMOperator::ForAllValuesStringNotEqualsIgnoreCase
447            | IAMOperator::ForAnyValueStringNotEquals
448            | IAMOperator::ForAnyValueStringNotEqualsIgnoreCase
449            | IAMOperator::ForAllValuesStringLike
450            | IAMOperator::ForAnyValueStringLike
451            | IAMOperator::ForAllValuesStringNotLike
452            | IAMOperator::ForAnyValueStringNotLike
453            | IAMOperator::StringEqualsIfExists
454            | IAMOperator::StringNotEqualsIfExists
455            | IAMOperator::StringEqualsIgnoreCaseIfExists
456            | IAMOperator::StringNotEqualsIgnoreCaseIfExists
457            | IAMOperator::StringLikeIfExists
458            | IAMOperator::StringNotLikeIfExists => OperatorType::String,
459
460            // Numeric operators
461            IAMOperator::NumericEquals
462            | IAMOperator::NumericNotEquals
463            | IAMOperator::NumericLessThan
464            | IAMOperator::NumericLessThanEquals
465            | IAMOperator::NumericGreaterThan
466            | IAMOperator::NumericGreaterThanEquals
467            | IAMOperator::NumericEqualsIfExists
468            | IAMOperator::NumericNotEqualsIfExists
469            | IAMOperator::NumericLessThanIfExists
470            | IAMOperator::NumericLessThanEqualsIfExists
471            | IAMOperator::NumericGreaterThanIfExists
472            | IAMOperator::NumericGreaterThanEqualsIfExists => OperatorType::Numeric,
473
474            // Date operators
475            IAMOperator::DateEquals
476            | IAMOperator::DateNotEquals
477            | IAMOperator::DateLessThan
478            | IAMOperator::DateLessThanEquals
479            | IAMOperator::DateGreaterThan
480            | IAMOperator::DateGreaterThanEquals
481            | IAMOperator::DateEqualsIfExists
482            | IAMOperator::DateNotEqualsIfExists
483            | IAMOperator::DateLessThanIfExists
484            | IAMOperator::DateLessThanEqualsIfExists
485            | IAMOperator::DateGreaterThanIfExists
486            | IAMOperator::DateGreaterThanEqualsIfExists => OperatorType::Date,
487
488            // Boolean operators
489            IAMOperator::Bool
490            | IAMOperator::ForAllValuesBool
491            | IAMOperator::ForAnyValueBool
492            | IAMOperator::BoolIfExists => OperatorType::Boolean,
493
494            // Binary operators
495            IAMOperator::BinaryEquals | IAMOperator::BinaryEqualsIfExists => OperatorType::Binary,
496
497            // IP address operators
498            IAMOperator::IpAddress
499            | IAMOperator::NotIpAddress
500            | IAMOperator::IpAddressIfExists
501            | IAMOperator::NotIpAddressIfExists => OperatorType::IpAddress,
502
503            // ARN operators
504            IAMOperator::ArnEquals
505            | IAMOperator::ArnLike
506            | IAMOperator::ArnNotEquals
507            | IAMOperator::ArnNotLike
508            | IAMOperator::ForAllValuesArnEquals
509            | IAMOperator::ForAllValuesArnLike
510            | IAMOperator::ForAnyValueArnEquals
511            | IAMOperator::ForAnyValueArnLike
512            | IAMOperator::ForAllValuesArnNotEquals
513            | IAMOperator::ForAllValuesArnNotLike
514            | IAMOperator::ForAnyValueArnNotEquals
515            | IAMOperator::ForAnyValueArnNotLike
516            | IAMOperator::ArnEqualsIfExists
517            | IAMOperator::ArnLikeIfExists
518            | IAMOperator::ArnNotEqualsIfExists
519            | IAMOperator::ArnNotLikeIfExists => OperatorType::Arn,
520
521            // Null operator
522            IAMOperator::Null => OperatorType::Null,
523        }
524    }
525
526    /// Returns the string representation of the operator for use in JSON
527    #[must_use]
528    pub fn as_str(&self) -> &'static str {
529        match self {
530            IAMOperator::StringEquals => "StringEquals",
531            IAMOperator::StringNotEquals => "StringNotEquals",
532            IAMOperator::StringEqualsIgnoreCase => "StringEqualsIgnoreCase",
533            IAMOperator::StringNotEqualsIgnoreCase => "StringNotEqualsIgnoreCase",
534            IAMOperator::StringLike => "StringLike",
535            IAMOperator::StringNotLike => "StringNotLike",
536            IAMOperator::ForAllValuesStringEquals => "ForAllValues:StringEquals",
537            IAMOperator::ForAllValuesStringEqualsIgnoreCase => {
538                "ForAllValues:StringEqualsIgnoreCase"
539            }
540            IAMOperator::ForAnyValueStringEquals => "ForAnyValue:StringEquals",
541            IAMOperator::ForAnyValueStringEqualsIgnoreCase => "ForAnyValue:StringEqualsIgnoreCase",
542            IAMOperator::ForAllValuesStringNotEquals => "ForAllValues:StringNotEquals",
543            IAMOperator::ForAllValuesStringNotEqualsIgnoreCase => {
544                "ForAllValues:StringNotEqualsIgnoreCase"
545            }
546            IAMOperator::ForAnyValueStringNotEquals => "ForAnyValue:StringNotEquals",
547            IAMOperator::ForAnyValueStringNotEqualsIgnoreCase => {
548                "ForAnyValue:StringNotEqualsIgnoreCase"
549            }
550            IAMOperator::ForAllValuesStringLike => "ForAllValues:StringLike",
551            IAMOperator::ForAnyValueStringLike => "ForAnyValue:StringLike",
552            IAMOperator::ForAllValuesStringNotLike => "ForAllValues:StringNotLike",
553            IAMOperator::ForAnyValueStringNotLike => "ForAnyValue:StringNotLike",
554            IAMOperator::NumericEquals => "NumericEquals",
555            IAMOperator::NumericNotEquals => "NumericNotEquals",
556            IAMOperator::NumericLessThan => "NumericLessThan",
557            IAMOperator::NumericLessThanEquals => "NumericLessThanEquals",
558            IAMOperator::NumericGreaterThan => "NumericGreaterThan",
559            IAMOperator::NumericGreaterThanEquals => "NumericGreaterThanEquals",
560            IAMOperator::DateEquals => "DateEquals",
561            IAMOperator::DateNotEquals => "DateNotEquals",
562            IAMOperator::DateLessThan => "DateLessThan",
563            IAMOperator::DateLessThanEquals => "DateLessThanEquals",
564            IAMOperator::DateGreaterThan => "DateGreaterThan",
565            IAMOperator::DateGreaterThanEquals => "DateGreaterThanEquals",
566            IAMOperator::Bool => "Bool",
567            IAMOperator::ForAllValuesBool => "ForAllValues:Bool",
568            IAMOperator::ForAnyValueBool => "ForAnyValue:Bool",
569            IAMOperator::BinaryEquals => "BinaryEquals",
570            IAMOperator::IpAddress => "IpAddress",
571            IAMOperator::NotIpAddress => "NotIpAddress",
572            IAMOperator::ArnEquals => "ArnEquals",
573            IAMOperator::ArnLike => "ArnLike",
574            IAMOperator::ArnNotEquals => "ArnNotEquals",
575            IAMOperator::ArnNotLike => "ArnNotLike",
576            IAMOperator::ForAllValuesArnEquals => "ForAllValues:ArnEquals",
577            IAMOperator::ForAllValuesArnLike => "ForAllValues:ArnLike",
578            IAMOperator::ForAnyValueArnEquals => "ForAnyValue:ArnEquals",
579            IAMOperator::ForAnyValueArnLike => "ForAnyValue:ArnLike",
580            IAMOperator::ForAllValuesArnNotEquals => "ForAllValues:ArnNotEquals",
581            IAMOperator::ForAllValuesArnNotLike => "ForAllValues:ArnNotLike",
582            IAMOperator::ForAnyValueArnNotEquals => "ForAnyValue:ArnNotEquals",
583            IAMOperator::ForAnyValueArnNotLike => "ForAnyValue:ArnNotLike",
584            IAMOperator::Null => "Null",
585            IAMOperator::StringEqualsIfExists => "StringEqualsIfExists",
586            IAMOperator::StringNotEqualsIfExists => "StringNotEqualsIfExists",
587            IAMOperator::StringEqualsIgnoreCaseIfExists => "StringEqualsIgnoreCaseIfExists",
588            IAMOperator::StringNotEqualsIgnoreCaseIfExists => "StringNotEqualsIgnoreCaseIfExists",
589            IAMOperator::StringLikeIfExists => "StringLikeIfExists",
590            IAMOperator::StringNotLikeIfExists => "StringNotLikeIfExists",
591            IAMOperator::NumericEqualsIfExists => "NumericEqualsIfExists",
592            IAMOperator::NumericNotEqualsIfExists => "NumericNotEqualsIfExists",
593            IAMOperator::NumericLessThanIfExists => "NumericLessThanIfExists",
594            IAMOperator::NumericLessThanEqualsIfExists => "NumericLessThanEqualsIfExists",
595            IAMOperator::NumericGreaterThanIfExists => "NumericGreaterThanIfExists",
596            IAMOperator::NumericGreaterThanEqualsIfExists => "NumericGreaterThanEqualsIfExists",
597            IAMOperator::DateEqualsIfExists => "DateEqualsIfExists",
598            IAMOperator::DateNotEqualsIfExists => "DateNotEqualsIfExists",
599            IAMOperator::DateLessThanIfExists => "DateLessThanIfExists",
600            IAMOperator::DateLessThanEqualsIfExists => "DateLessThanEqualsIfExists",
601            IAMOperator::DateGreaterThanIfExists => "DateGreaterThanIfExists",
602            IAMOperator::DateGreaterThanEqualsIfExists => "DateGreaterThanEqualsIfExists",
603            IAMOperator::BoolIfExists => "BoolIfExists",
604            IAMOperator::BinaryEqualsIfExists => "BinaryEqualsIfExists",
605            IAMOperator::IpAddressIfExists => "IpAddressIfExists",
606            IAMOperator::NotIpAddressIfExists => "NotIpAddressIfExists",
607            IAMOperator::ArnEqualsIfExists => "ArnEqualsIfExists",
608            IAMOperator::ArnLikeIfExists => "ArnLikeIfExists",
609            IAMOperator::ArnNotEqualsIfExists => "ArnNotEqualsIfExists",
610            IAMOperator::ArnNotLikeIfExists => "ArnNotLikeIfExists",
611        }
612    }
613}
614
615impl std::fmt::Display for IAMOperator {
616    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
617        write!(f, "{}", self.as_str())
618    }
619}
620
621impl std::str::FromStr for IAMOperator {
622    type Err = String;
623
624    fn from_str(s: &str) -> Result<Self, Self::Err> {
625        match s {
626            "StringEquals" => Ok(IAMOperator::StringEquals),
627            "StringNotEquals" => Ok(IAMOperator::StringNotEquals),
628            "StringEqualsIgnoreCase" => Ok(IAMOperator::StringEqualsIgnoreCase),
629            "StringNotEqualsIgnoreCase" => Ok(IAMOperator::StringNotEqualsIgnoreCase),
630            "StringLike" => Ok(IAMOperator::StringLike),
631            "StringNotLike" => Ok(IAMOperator::StringNotLike),
632            "ForAllValues:StringEquals" => Ok(IAMOperator::ForAllValuesStringEquals),
633            "ForAllValues:StringEqualsIgnoreCase" => {
634                Ok(IAMOperator::ForAllValuesStringEqualsIgnoreCase)
635            }
636            "ForAnyValue:StringEquals" => Ok(IAMOperator::ForAnyValueStringEquals),
637            "ForAnyValue:StringEqualsIgnoreCase" => {
638                Ok(IAMOperator::ForAnyValueStringEqualsIgnoreCase)
639            }
640            "ForAllValues:StringNotEquals" => Ok(IAMOperator::ForAllValuesStringNotEquals),
641            "ForAllValues:StringNotEqualsIgnoreCase" => {
642                Ok(IAMOperator::ForAllValuesStringNotEqualsIgnoreCase)
643            }
644            "ForAnyValue:StringNotEquals" => Ok(IAMOperator::ForAnyValueStringNotEquals),
645            "ForAnyValue:StringNotEqualsIgnoreCase" => {
646                Ok(IAMOperator::ForAnyValueStringNotEqualsIgnoreCase)
647            }
648            "ForAllValues:StringLike" => Ok(IAMOperator::ForAllValuesStringLike),
649            "ForAnyValue:StringLike" => Ok(IAMOperator::ForAnyValueStringLike),
650            "ForAllValues:StringNotLike" => Ok(IAMOperator::ForAllValuesStringNotLike),
651            "ForAnyValue:StringNotLike" => Ok(IAMOperator::ForAnyValueStringNotLike),
652            "NumericEquals" => Ok(IAMOperator::NumericEquals),
653            "NumericNotEquals" => Ok(IAMOperator::NumericNotEquals),
654            "NumericLessThan" => Ok(IAMOperator::NumericLessThan),
655            "NumericLessThanEquals" => Ok(IAMOperator::NumericLessThanEquals),
656            "NumericGreaterThan" => Ok(IAMOperator::NumericGreaterThan),
657            "NumericGreaterThanEquals" => Ok(IAMOperator::NumericGreaterThanEquals),
658            "DateEquals" => Ok(IAMOperator::DateEquals),
659            "DateNotEquals" => Ok(IAMOperator::DateNotEquals),
660            "DateLessThan" => Ok(IAMOperator::DateLessThan),
661            "DateLessThanEquals" => Ok(IAMOperator::DateLessThanEquals),
662            "DateGreaterThan" => Ok(IAMOperator::DateGreaterThan),
663            "DateGreaterThanEquals" => Ok(IAMOperator::DateGreaterThanEquals),
664            "Bool" => Ok(IAMOperator::Bool),
665            "ForAllValues:Bool" => Ok(IAMOperator::ForAllValuesBool),
666            "ForAnyValue:Bool" => Ok(IAMOperator::ForAnyValueBool),
667            "BinaryEquals" => Ok(IAMOperator::BinaryEquals),
668            "IpAddress" => Ok(IAMOperator::IpAddress),
669            "NotIpAddress" => Ok(IAMOperator::NotIpAddress),
670            "ArnEquals" => Ok(IAMOperator::ArnEquals),
671            "ArnLike" => Ok(IAMOperator::ArnLike),
672            "ArnNotEquals" => Ok(IAMOperator::ArnNotEquals),
673            "ArnNotLike" => Ok(IAMOperator::ArnNotLike),
674            "ForAllValues:ArnEquals" => Ok(IAMOperator::ForAllValuesArnEquals),
675            "ForAllValues:ArnLike" => Ok(IAMOperator::ForAllValuesArnLike),
676            "ForAnyValue:ArnEquals" => Ok(IAMOperator::ForAnyValueArnEquals),
677            "ForAnyValue:ArnLike" => Ok(IAMOperator::ForAnyValueArnLike),
678            "ForAllValues:ArnNotEquals" => Ok(IAMOperator::ForAllValuesArnNotEquals),
679            "ForAllValues:ArnNotLike" => Ok(IAMOperator::ForAllValuesArnNotLike),
680            "ForAnyValue:ArnNotEquals" => Ok(IAMOperator::ForAnyValueArnNotEquals),
681            "ForAnyValue:ArnNotLike" => Ok(IAMOperator::ForAnyValueArnNotLike),
682            "Null" => Ok(IAMOperator::Null),
683            "StringEqualsIfExists" => Ok(IAMOperator::StringEqualsIfExists),
684            "StringNotEqualsIfExists" => Ok(IAMOperator::StringNotEqualsIfExists),
685            "StringEqualsIgnoreCaseIfExists" => Ok(IAMOperator::StringEqualsIgnoreCaseIfExists),
686            "StringNotEqualsIgnoreCaseIfExists" => {
687                Ok(IAMOperator::StringNotEqualsIgnoreCaseIfExists)
688            }
689            "StringLikeIfExists" => Ok(IAMOperator::StringLikeIfExists),
690            "StringNotLikeIfExists" => Ok(IAMOperator::StringNotLikeIfExists),
691            "NumericEqualsIfExists" => Ok(IAMOperator::NumericEqualsIfExists),
692            "NumericNotEqualsIfExists" => Ok(IAMOperator::NumericNotEqualsIfExists),
693            "NumericLessThanIfExists" => Ok(IAMOperator::NumericLessThanIfExists),
694            "NumericLessThanEqualsIfExists" => Ok(IAMOperator::NumericLessThanEqualsIfExists),
695            "NumericGreaterThanIfExists" => Ok(IAMOperator::NumericGreaterThanIfExists),
696            "NumericGreaterThanEqualsIfExists" => Ok(IAMOperator::NumericGreaterThanEqualsIfExists),
697            "DateEqualsIfExists" => Ok(IAMOperator::DateEqualsIfExists),
698            "DateNotEqualsIfExists" => Ok(IAMOperator::DateNotEqualsIfExists),
699            "DateLessThanIfExists" => Ok(IAMOperator::DateLessThanIfExists),
700            "DateLessThanEqualsIfExists" => Ok(IAMOperator::DateLessThanEqualsIfExists),
701            "DateGreaterThanIfExists" => Ok(IAMOperator::DateGreaterThanIfExists),
702            "DateGreaterThanEqualsIfExists" => Ok(IAMOperator::DateGreaterThanEqualsIfExists),
703            "BoolIfExists" => Ok(IAMOperator::BoolIfExists),
704            "BinaryEqualsIfExists" => Ok(IAMOperator::BinaryEqualsIfExists),
705            "IpAddressIfExists" => Ok(IAMOperator::IpAddressIfExists),
706            "NotIpAddressIfExists" => Ok(IAMOperator::NotIpAddressIfExists),
707            "ArnEqualsIfExists" => Ok(IAMOperator::ArnEqualsIfExists),
708            "ArnLikeIfExists" => Ok(IAMOperator::ArnLikeIfExists),
709            "ArnNotEqualsIfExists" => Ok(IAMOperator::ArnNotEqualsIfExists),
710            "ArnNotLikeIfExists" => Ok(IAMOperator::ArnNotLikeIfExists),
711            _ => Err(format!("Unknown operator: {s}")),
712        }
713    }
714}
715
716#[cfg(test)]
717mod tests {
718    use super::*;
719
720    #[test]
721    fn test_operator_serialization() {
722        let operator = IAMOperator::StringEquals;
723        let json = serde_json::to_string(&operator).unwrap();
724        assert_eq!(json, "\"StringEquals\"");
725
726        let deserialized: IAMOperator = serde_json::from_str(&json).unwrap();
727        assert_eq!(operator, deserialized);
728    }
729
730    #[test]
731    fn test_multivalued_operators() {
732        let operator = IAMOperator::ForAllValuesStringEquals;
733        let json = serde_json::to_string(&operator).unwrap();
734        assert_eq!(json, "\"ForAllValues:StringEquals\"");
735
736        let deserialized: IAMOperator = serde_json::from_str(&json).unwrap();
737        assert_eq!(operator, deserialized);
738    }
739
740    #[test]
741    fn test_operator_categories() {
742        assert!(IAMOperator::StringEquals.is_string_operator());
743        assert!(IAMOperator::NumericEquals.is_numeric_operator());
744        assert!(IAMOperator::DateEquals.is_date_operator());
745        assert!(IAMOperator::Bool.is_boolean_operator());
746        assert!(IAMOperator::ArnEquals.is_arn_operator());
747        assert!(IAMOperator::IpAddress.is_ip_operator());
748        assert!(IAMOperator::BinaryEquals.is_binary_operator());
749    }
750
751    #[test]
752    fn test_operator_features() {
753        assert!(IAMOperator::StringLike.supports_wildcards());
754        assert!(IAMOperator::StringEquals.supports_policy_variables());
755        assert!(!IAMOperator::NumericEquals.supports_policy_variables());
756        assert!(IAMOperator::ForAllValuesStringEquals.is_multivalued_operator());
757        assert!(IAMOperator::StringEqualsIfExists.is_if_exists_operator());
758        assert!(IAMOperator::StringNotEquals.is_negated_operator());
759
760        // Test multiple values support
761        assert!(IAMOperator::StringEquals.supports_multiple_values());
762        assert!(IAMOperator::StringNotEquals.supports_multiple_values());
763        assert!(IAMOperator::NumericEquals.supports_multiple_values());
764        assert!(IAMOperator::DateEquals.supports_multiple_values());
765        assert!(IAMOperator::IpAddress.supports_multiple_values());
766        assert!(IAMOperator::ArnEquals.supports_multiple_values());
767
768        // These should not support multiple values
769        assert!(!IAMOperator::Bool.supports_multiple_values());
770        assert!(!IAMOperator::BoolIfExists.supports_multiple_values());
771        assert!(!IAMOperator::Null.supports_multiple_values());
772    }
773
774    #[test]
775    fn test_supports_policy_variables() {
776        // AWS: variables work only with string and ARN condition operators.
777        assert!(IAMOperator::StringEquals.supports_policy_variables());
778        assert!(IAMOperator::StringLikeIfExists.supports_policy_variables());
779        assert!(IAMOperator::ArnLike.supports_policy_variables());
780        assert!(IAMOperator::ForAnyValueArnEquals.supports_policy_variables());
781
782        // Not supported for boolean, null, numeric, date, binary, or IP.
783        assert!(!IAMOperator::Bool.supports_policy_variables());
784        assert!(!IAMOperator::ForAllValuesBool.supports_policy_variables());
785        assert!(!IAMOperator::ForAnyValueBool.supports_policy_variables());
786        assert!(!IAMOperator::Null.supports_policy_variables());
787        assert!(!IAMOperator::NumericEquals.supports_policy_variables());
788        assert!(!IAMOperator::DateEquals.supports_policy_variables());
789        assert!(!IAMOperator::BinaryEquals.supports_policy_variables());
790        assert!(!IAMOperator::IpAddress.supports_policy_variables());
791        assert!(!IAMOperator::NotIpAddress.supports_policy_variables());
792    }
793
794    #[test]
795    fn test_operator_category_strings() {
796        assert_eq!(IAMOperator::StringEquals.category(), OperatorType::String);
797        assert_eq!(IAMOperator::NumericEquals.category(), OperatorType::Numeric);
798        assert_eq!(IAMOperator::DateEquals.category(), OperatorType::Date);
799        assert_eq!(IAMOperator::Bool.category(), OperatorType::Boolean);
800        assert_eq!(IAMOperator::BinaryEquals.category(), OperatorType::Binary);
801        assert_eq!(IAMOperator::IpAddress.category(), OperatorType::IpAddress);
802        assert_eq!(IAMOperator::ArnEquals.category(), OperatorType::Arn);
803        assert_eq!(IAMOperator::Null.category(), OperatorType::Null);
804    }
805
806    #[test]
807    #[allow(clippy::too_many_lines)]
808    fn test_operator_category_exhaustive() {
809        // Every `IAMOperator` variant must map to exactly one category. The
810        // exhaustive match in `category()` guarantees there is no unknown
811        // operator, so `category()` can never panic; this test documents and
812        // locks in the full mapping.
813        fn assert_category(ops: &[IAMOperator], expected: &OperatorType) {
814            for op in ops {
815                assert_eq!(&op.category(), expected, "operator {}", op.as_str());
816            }
817        }
818
819        assert_category(
820            &[
821                IAMOperator::StringEquals,
822                IAMOperator::StringNotEquals,
823                IAMOperator::StringEqualsIgnoreCase,
824                IAMOperator::StringNotEqualsIgnoreCase,
825                IAMOperator::StringLike,
826                IAMOperator::StringNotLike,
827                IAMOperator::ForAllValuesStringEquals,
828                IAMOperator::ForAllValuesStringEqualsIgnoreCase,
829                IAMOperator::ForAnyValueStringEquals,
830                IAMOperator::ForAnyValueStringEqualsIgnoreCase,
831                IAMOperator::ForAllValuesStringNotEquals,
832                IAMOperator::ForAllValuesStringNotEqualsIgnoreCase,
833                IAMOperator::ForAnyValueStringNotEquals,
834                IAMOperator::ForAnyValueStringNotEqualsIgnoreCase,
835                IAMOperator::ForAllValuesStringLike,
836                IAMOperator::ForAnyValueStringLike,
837                IAMOperator::ForAllValuesStringNotLike,
838                IAMOperator::ForAnyValueStringNotLike,
839                IAMOperator::StringEqualsIfExists,
840                IAMOperator::StringNotEqualsIfExists,
841                IAMOperator::StringEqualsIgnoreCaseIfExists,
842                IAMOperator::StringNotEqualsIgnoreCaseIfExists,
843                IAMOperator::StringLikeIfExists,
844                IAMOperator::StringNotLikeIfExists,
845            ],
846            &OperatorType::String,
847        );
848
849        assert_category(
850            &[
851                IAMOperator::NumericEquals,
852                IAMOperator::NumericNotEquals,
853                IAMOperator::NumericLessThan,
854                IAMOperator::NumericLessThanEquals,
855                IAMOperator::NumericGreaterThan,
856                IAMOperator::NumericGreaterThanEquals,
857                IAMOperator::NumericEqualsIfExists,
858                IAMOperator::NumericNotEqualsIfExists,
859                IAMOperator::NumericLessThanIfExists,
860                IAMOperator::NumericLessThanEqualsIfExists,
861                IAMOperator::NumericGreaterThanIfExists,
862                IAMOperator::NumericGreaterThanEqualsIfExists,
863            ],
864            &OperatorType::Numeric,
865        );
866
867        assert_category(
868            &[
869                IAMOperator::DateEquals,
870                IAMOperator::DateNotEquals,
871                IAMOperator::DateLessThan,
872                IAMOperator::DateLessThanEquals,
873                IAMOperator::DateGreaterThan,
874                IAMOperator::DateGreaterThanEquals,
875                IAMOperator::DateEqualsIfExists,
876                IAMOperator::DateNotEqualsIfExists,
877                IAMOperator::DateLessThanIfExists,
878                IAMOperator::DateLessThanEqualsIfExists,
879                IAMOperator::DateGreaterThanIfExists,
880                IAMOperator::DateGreaterThanEqualsIfExists,
881            ],
882            &OperatorType::Date,
883        );
884
885        assert_category(
886            &[
887                IAMOperator::Bool,
888                IAMOperator::ForAllValuesBool,
889                IAMOperator::ForAnyValueBool,
890                IAMOperator::BoolIfExists,
891            ],
892            &OperatorType::Boolean,
893        );
894
895        assert_category(
896            &[IAMOperator::BinaryEquals, IAMOperator::BinaryEqualsIfExists],
897            &OperatorType::Binary,
898        );
899
900        assert_category(
901            &[
902                IAMOperator::IpAddress,
903                IAMOperator::NotIpAddress,
904                IAMOperator::IpAddressIfExists,
905                IAMOperator::NotIpAddressIfExists,
906            ],
907            &OperatorType::IpAddress,
908        );
909
910        assert_category(
911            &[
912                IAMOperator::ArnEquals,
913                IAMOperator::ArnLike,
914                IAMOperator::ArnNotEquals,
915                IAMOperator::ArnNotLike,
916                IAMOperator::ForAllValuesArnEquals,
917                IAMOperator::ForAllValuesArnLike,
918                IAMOperator::ForAnyValueArnEquals,
919                IAMOperator::ForAnyValueArnLike,
920                IAMOperator::ForAllValuesArnNotEquals,
921                IAMOperator::ForAllValuesArnNotLike,
922                IAMOperator::ForAnyValueArnNotEquals,
923                IAMOperator::ForAnyValueArnNotLike,
924                IAMOperator::ArnEqualsIfExists,
925                IAMOperator::ArnLikeIfExists,
926                IAMOperator::ArnNotEqualsIfExists,
927                IAMOperator::ArnNotLikeIfExists,
928            ],
929            &OperatorType::Arn,
930        );
931
932        assert_category(&[IAMOperator::Null], &OperatorType::Null);
933    }
934
935    #[test]
936    fn test_operator_string_conversion() {
937        assert_eq!(IAMOperator::StringEquals.as_str(), "StringEquals");
938        assert_eq!(
939            IAMOperator::ForAllValuesStringEquals.as_str(),
940            "ForAllValues:StringEquals"
941        );
942        assert_eq!(
943            IAMOperator::StringEqualsIfExists.as_str(),
944            "StringEqualsIfExists"
945        );
946    }
947
948    #[test]
949    fn test_operator_from_str() {
950        assert_eq!(
951            "StringEquals".parse::<IAMOperator>().unwrap(),
952            IAMOperator::StringEquals
953        );
954        assert_eq!(
955            "ForAllValues:StringEquals".parse::<IAMOperator>().unwrap(),
956            IAMOperator::ForAllValuesStringEquals
957        );
958        assert_eq!(
959            "StringEqualsIfExists".parse::<IAMOperator>().unwrap(),
960            IAMOperator::StringEqualsIfExists
961        );
962
963        assert!("InvalidOperator".parse::<IAMOperator>().is_err());
964    }
965}