1use crate::{
2 core::Operator,
3 validation::{Validate, ValidationContext, ValidationResult, ValidationError, helpers},
4};
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::collections::{BTreeMap, HashMap};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct Condition {
11 pub operator: Operator,
13 pub key: String,
15 pub value: serde_json::Value,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ConditionBlock {
23 pub conditions: HashMap<Operator, HashMap<String, serde_json::Value>>,
25}
26
27impl Serialize for ConditionBlock {
28 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29 where
30 S: Serializer,
31 {
32 let ordered_map: BTreeMap<String, BTreeMap<String, &serde_json::Value>> = self
35 .conditions
36 .iter()
37 .map(|(op, conditions)| {
38 let inner_ordered: BTreeMap<String, &serde_json::Value> = conditions
39 .iter()
40 .map(|(k, v)| (k.clone(), v))
41 .collect();
42 (op.as_str().to_string(), inner_ordered)
43 })
44 .collect();
45
46 ordered_map.serialize(serializer)
47 }
48}
49
50impl<'de> Deserialize<'de> for ConditionBlock {
51 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
52 where
53 D: Deserializer<'de>,
54 {
55 let string_map: HashMap<String, HashMap<String, serde_json::Value>> =
56 HashMap::deserialize(deserializer)?;
57
58 let mut conditions = HashMap::new();
59
60 for (op_str, condition_map) in string_map {
61 let operator = op_str.parse::<Operator>().map_err(|e| {
62 serde::de::Error::custom(format!("Invalid operator '{}': {}", op_str, e))
63 })?;
64 conditions.insert(operator, condition_map);
65 }
66
67 Ok(ConditionBlock { conditions })
68 }
69}
70
71impl Condition {
72 pub fn new<K: Into<String>>(operator: Operator, key: K, value: serde_json::Value) -> Self {
74 Self {
75 operator,
76 key: key.into(),
77 value,
78 }
79 }
80
81 pub fn string<K: Into<String>, V: Into<String>>(operator: Operator, key: K, value: V) -> Self {
83 Self::new(operator, key, serde_json::Value::String(value.into()))
84 }
85
86 pub fn boolean<K: Into<String>>(operator: Operator, key: K, value: bool) -> Self {
88 Self::new(operator, key, serde_json::Value::Bool(value))
89 }
90
91 pub fn number<K: Into<String>>(operator: Operator, key: K, value: i64) -> Self {
93 Self::new(
94 operator,
95 key,
96 serde_json::Value::Number(serde_json::Number::from(value)),
97 )
98 }
99
100 pub fn string_array<K: Into<String>>(operator: Operator, key: K, values: Vec<String>) -> Self {
102 let json_values: Vec<serde_json::Value> =
103 values.into_iter().map(serde_json::Value::String).collect();
104 Self::new(operator, key, serde_json::Value::Array(json_values))
105 }
106}
107
108impl ConditionBlock {
109 pub fn new() -> Self {
111 Self {
112 conditions: HashMap::new(),
113 }
114 }
115
116 pub fn add_condition(&mut self, condition: Condition) {
118 let operator_map = self
119 .conditions
120 .entry(condition.operator)
121 .or_insert_with(HashMap::new);
122 operator_map.insert(condition.key, condition.value);
123 }
124
125 pub fn with_condition(mut self, condition: Condition) -> Self {
127 self.add_condition(condition);
128 self
129 }
130
131 pub fn with_condition_direct<K: Into<String>>(
133 mut self,
134 operator: Operator,
135 key: K,
136 value: serde_json::Value,
137 ) -> Self {
138 let condition = Condition::new(operator, key, value);
139 self.add_condition(condition);
140 self
141 }
142
143 pub fn get_conditions_for_operator(
145 &self,
146 operator: &Operator,
147 ) -> Option<&HashMap<String, serde_json::Value>> {
148 self.conditions.get(operator)
149 }
150
151 pub fn get_condition_value(
153 &self,
154 operator: &Operator,
155 key: &str,
156 ) -> Option<&serde_json::Value> {
157 self.conditions.get(operator)?.get(key)
158 }
159
160 pub fn has_condition(&self, operator: &Operator, key: &str) -> bool {
162 self.conditions
163 .get(operator)
164 .map(|map| map.contains_key(key))
165 .unwrap_or(false)
166 }
167
168 pub fn operators(&self) -> Vec<&Operator> {
170 self.conditions.keys().collect()
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.conditions.is_empty()
176 }
177
178 pub fn to_legacy_format(&self) -> HashMap<String, HashMap<String, serde_json::Value>> {
180 self.conditions
181 .iter()
182 .map(|(op, conditions)| (op.as_str().to_string(), conditions.clone()))
183 .collect()
184 }
185
186 pub fn from_legacy_format(
188 legacy: HashMap<String, HashMap<String, serde_json::Value>>,
189 ) -> Result<Self, String> {
190 let mut conditions = HashMap::new();
191
192 for (op_str, condition_map) in legacy {
193 let operator = op_str
194 .parse::<Operator>()
195 .map_err(|e| format!("Invalid operator '{}': {}", op_str, e))?;
196 conditions.insert(operator, condition_map);
197 }
198
199 Ok(Self { conditions })
200 }
201}
202
203impl Default for ConditionBlock {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209impl Validate for Condition {
210 fn validate(&self, context: &mut ValidationContext) -> ValidationResult {
211 context.with_segment("Condition", |ctx| {
212 let mut results = Vec::new();
213
214 results.push(helpers::validate_non_empty(&self.key, "key", ctx));
216
217 match &self.value {
219 serde_json::Value::Null => {
220 results.push(Err(ValidationError::InvalidCondition {
221 operator: self.operator.as_str().to_string(),
222 key: self.key.clone(),
223 reason: "Condition value cannot be null".to_string(),
224 }));
225 }
226 serde_json::Value::Array(arr) => {
227 if arr.is_empty() {
228 results.push(Err(ValidationError::InvalidCondition {
229 operator: self.operator.as_str().to_string(),
230 key: self.key.clone(),
231 reason: "Condition value array cannot be empty".to_string(),
232 }));
233 }
234
235 if !self.operator.supports_multiple_values() && arr.len() > 1 {
237 results.push(Err(ValidationError::InvalidCondition {
238 operator: self.operator.as_str().to_string(),
239 key: self.key.clone(),
240 reason: format!("Operator {} does not support multiple values", self.operator.as_str()),
241 }));
242 }
243 }
244 _ => {} }
246
247 match self.operator.category() {
249 "String" => {
250 match &self.value {
252 serde_json::Value::String(_) => {},
253 serde_json::Value::Array(arr) => {
254 for (i, val) in arr.iter().enumerate() {
255 if !val.is_string() {
256 results.push(Err(ValidationError::InvalidCondition {
257 operator: self.operator.as_str().to_string(),
258 key: self.key.clone(),
259 reason: format!("String operator requires string values, found {} at index {}", val, i),
260 }));
261 }
262 }
263 },
264 _ => {
265 results.push(Err(ValidationError::InvalidCondition {
266 operator: self.operator.as_str().to_string(),
267 key: self.key.clone(),
268 reason: "String operator requires string value(s)".to_string(),
269 }));
270 }
271 }
272 },
273 "Numeric" => {
274 match &self.value {
276 serde_json::Value::Number(_) => {},
277 serde_json::Value::String(s) => {
278 if s.parse::<f64>().is_err() {
280 results.push(Err(ValidationError::InvalidCondition {
281 operator: self.operator.as_str().to_string(),
282 key: self.key.clone(),
283 reason: format!("Numeric operator requires numeric value, found non-numeric string: {}", s),
284 }));
285 }
286 },
287 serde_json::Value::Array(arr) => {
288 for (i, val) in arr.iter().enumerate() {
289 match val {
290 serde_json::Value::Number(_) => {},
291 serde_json::Value::String(s) => {
292 if s.parse::<f64>().is_err() {
293 results.push(Err(ValidationError::InvalidCondition {
294 operator: self.operator.as_str().to_string(),
295 key: self.key.clone(),
296 reason: format!("Numeric operator requires numeric values, found non-numeric string at index {}: {}", i, s),
297 }));
298 }
299 },
300 _ => {
301 results.push(Err(ValidationError::InvalidCondition {
302 operator: self.operator.as_str().to_string(),
303 key: self.key.clone(),
304 reason: format!("Numeric operator requires numeric values, found {} at index {}", val, i),
305 }));
306 }
307 }
308 }
309 },
310 _ => {
311 results.push(Err(ValidationError::InvalidCondition {
312 operator: self.operator.as_str().to_string(),
313 key: self.key.clone(),
314 reason: "Numeric operator requires numeric value(s)".to_string(),
315 }));
316 }
317 }
318 },
319 "Date" => {
320 match &self.value {
322 serde_json::Value::String(s) => {
323 if !s.contains('T') && !s.contains('-') {
325 results.push(Err(ValidationError::InvalidCondition {
326 operator: self.operator.as_str().to_string(),
327 key: self.key.clone(),
328 reason: format!("Date operator requires ISO 8601 date format, found: {}", s),
329 }));
330 }
331 },
332 _ => {
333 results.push(Err(ValidationError::InvalidCondition {
334 operator: self.operator.as_str().to_string(),
335 key: self.key.clone(),
336 reason: "Date operator requires string date value".to_string(),
337 }));
338 }
339 }
340 },
341 "Boolean" => {
342 match &self.value {
344 serde_json::Value::Bool(_) => {},
345 serde_json::Value::String(s) => {
346 if !matches!(s.as_str(), "true" | "false") {
347 results.push(Err(ValidationError::InvalidCondition {
348 operator: self.operator.as_str().to_string(),
349 key: self.key.clone(),
350 reason: format!("Boolean operator requires boolean value, found: {}", s),
351 }));
352 }
353 },
354 _ => {
355 results.push(Err(ValidationError::InvalidCondition {
356 operator: self.operator.as_str().to_string(),
357 key: self.key.clone(),
358 reason: "Boolean operator requires boolean value".to_string(),
359 }));
360 }
361 }
362 },
363 _ => {} }
365
366 helpers::collect_errors(results)
367 })
368 }
369}
370
371impl Validate for ConditionBlock {
372 fn validate(&self, context: &mut ValidationContext) -> ValidationResult {
373 context.with_segment("ConditionBlock", |ctx| {
374 if self.conditions.is_empty() {
375 return Err(ValidationError::InvalidValue {
376 field: "Condition".to_string(),
377 value: "{}".to_string(),
378 reason: "Condition block cannot be empty".to_string(),
379 });
380 }
381
382 let mut results = Vec::new();
383
384 for (operator, condition_map) in &self.conditions {
385 ctx.with_segment(&operator.as_str(), |op_ctx| {
386 if condition_map.is_empty() {
387 results.push(Err(ValidationError::InvalidValue {
388 field: "Condition operator".to_string(),
389 value: operator.as_str().to_string(),
390 reason: "Condition operator cannot have empty condition map".to_string(),
391 }));
392 return;
393 }
394
395 for (key, value) in condition_map {
396 op_ctx.with_segment(key, |key_ctx| {
397 let condition = Condition {
398 operator: operator.clone(),
399 key: key.clone(),
400 value: value.clone(),
401 };
402 results.push(condition.validate(key_ctx));
403 });
404 }
405 });
406 }
407
408 helpers::collect_errors(results)
409 })
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use serde_json::json;
417
418 #[test]
419 fn test_condition_creation() {
420 let condition = Condition::string(Operator::StringEquals, "aws:username", "john");
421
422 assert_eq!(condition.operator, Operator::StringEquals);
423 assert_eq!(condition.key, "aws:username");
424 assert_eq!(condition.value, json!("john"));
425 }
426
427 #[test]
428 fn test_condition_block() {
429 let block = ConditionBlock::new()
430 .with_condition(Condition::string(
431 Operator::StringEquals,
432 "aws:username",
433 "john",
434 ))
435 .with_condition(Condition::boolean(
436 Operator::Bool,
437 "aws:SecureTransport",
438 true,
439 ));
440
441 assert!(block.has_condition(&Operator::StringEquals, "aws:username"));
442 assert!(block.has_condition(&Operator::Bool, "aws:SecureTransport"));
443 assert!(!block.has_condition(&Operator::StringEquals, "nonexistent"));
444
445 let username = block.get_condition_value(&Operator::StringEquals, "aws:username");
446 assert_eq!(username, Some(&json!("john")));
447 }
448
449 #[test]
450 fn test_legacy_format_conversion() {
451 let mut legacy = HashMap::new();
452 let mut string_conditions = HashMap::new();
453 string_conditions.insert("aws:username".to_string(), json!("john"));
454 legacy.insert("StringEquals".to_string(), string_conditions);
455
456 let block = ConditionBlock::from_legacy_format(legacy.clone()).unwrap();
457 assert!(block.has_condition(&Operator::StringEquals, "aws:username"));
458
459 let converted_back = block.to_legacy_format();
460 assert_eq!(converted_back, legacy);
461 }
462
463 #[test]
464 fn test_condition_serialization() {
465 let condition = Condition::string(Operator::StringEquals, "aws:username", "john");
466
467 let json = serde_json::to_string(&condition).unwrap();
468 let deserialized: Condition = serde_json::from_str(&json).unwrap();
469
470 assert_eq!(condition, deserialized);
471 }
472
473 #[test]
474 fn test_condition_block_serialization() {
475 let block = ConditionBlock::new()
476 .with_condition(Condition::string_array(
477 Operator::StringEquals,
478 "aws:PrincipalTag/department",
479 vec!["finance".to_string(), "hr".to_string(), "legal".to_string()],
480 ))
481 .with_condition(Condition::string_array(
482 Operator::ArnLike,
483 "aws:PrincipalArn",
484 vec![
485 "arn:aws:iam::222222222222:user/Ana".to_string(),
486 "arn:aws:iam::222222222222:user/Mary".to_string(),
487 ],
488 ));
489
490 let json = serde_json::to_string_pretty(&block).unwrap();
491 println!("Current serialization:\n{}", json);
492
493 let deserialized: ConditionBlock = serde_json::from_str(&json).unwrap();
495 assert_eq!(block, deserialized);
496 }
497}