iam_rs/core/
resource.rs

1use crate::validation::{Validate, ValidationContext, ValidationResult, helpers};
2use serde::{Deserialize, Serialize};
3
4/// Represents a resource in an IAM policy
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(untagged)]
7#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
8pub enum IAMResource {
9    /// A single resource (e.g., "`arn:aws:s3:::bucket/*`")
10    Single(String),
11    /// Multiple resources
12    Multiple(Vec<String>),
13}
14
15impl Validate for IAMResource {
16    fn validate(&self, context: &mut ValidationContext) -> ValidationResult {
17        context.with_segment("Resource", |ctx| match self {
18            IAMResource::Single(resource) => helpers::validate_resource(resource, ctx),
19            IAMResource::Multiple(resources) => {
20                if resources.is_empty() {
21                    return Err(crate::validation::ValidationError::InvalidValue {
22                        field: "Resource".to_string(),
23                        value: "[]".to_string(),
24                        reason: "Resource list cannot be empty".to_string(),
25                    });
26                }
27
28                let results: Vec<ValidationResult> = resources
29                    .iter()
30                    .enumerate()
31                    .map(|(i, resource)| {
32                        ctx.with_segment(&format!("[{i}]"), |nested_ctx| {
33                            helpers::validate_resource(resource, nested_ctx)
34                        })
35                    })
36                    .collect();
37
38                helpers::collect_errors(results)
39            }
40        })
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_resource_validation() {
50        let valid_single = IAMResource::Single("arn:aws:s3:::bucket/*".to_string());
51        assert!(valid_single.is_valid());
52
53        let valid_wildcard = IAMResource::Single("*".to_string());
54        assert!(valid_wildcard.is_valid());
55
56        let valid_multiple = IAMResource::Multiple(vec![
57            "arn:aws:s3:::bucket/*".to_string(),
58            "arn:aws:s3:::other-bucket/*".to_string(),
59        ]);
60        assert!(valid_multiple.is_valid());
61
62        let invalid_single = IAMResource::Single("invalid-resource".to_string());
63        assert!(!invalid_single.is_valid());
64
65        let empty_multiple = IAMResource::Multiple(vec![]);
66        assert!(!empty_multiple.is_valid());
67
68        let invalid_multiple = IAMResource::Multiple(vec![
69            "arn:aws:s3:::bucket/*".to_string(),
70            "invalid-resource".to_string(),
71        ]);
72        assert!(!invalid_multiple.is_valid());
73    }
74
75    #[test]
76    fn test_resource_with_wildcards() {
77        let wildcard_resource = IAMResource::Single("arn:aws:s3:::*/*".to_string());
78        assert!(wildcard_resource.is_valid());
79
80        let complex_wildcard =
81            IAMResource::Single("arn:aws:s3:::bucket/folder/*/file.txt".to_string());
82        assert!(complex_wildcard.is_valid());
83    }
84}