Skip to main content

rc_core/
cors.rs

1//! Bucket CORS configuration types
2//!
3//! Domain types for S3 bucket CORS configuration and rules.
4
5use serde::{Deserialize, Serialize};
6
7/// Full CORS configuration for a bucket
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CorsConfiguration {
10    /// CORS rules
11    pub rules: Vec<CorsRule>,
12}
13
14/// A single bucket CORS rule
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct CorsRule {
18    /// Optional rule identifier
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub id: Option<String>,
21
22    /// Allowed request origins
23    pub allowed_origins: Vec<String>,
24
25    /// Allowed HTTP methods
26    pub allowed_methods: Vec<String>,
27
28    /// Allowed request headers
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub allowed_headers: Option<Vec<String>>,
31
32    /// Exposed response headers
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub expose_headers: Option<Vec<String>>,
35
36    /// Browser preflight cache duration in seconds
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub max_age_seconds: Option<i32>,
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn test_cors_rule_serialization() {
47        let rule = CorsRule {
48            id: Some("web-app".to_string()),
49            allowed_origins: vec!["https://app.example.com".to_string()],
50            allowed_methods: vec!["GET".to_string(), "PUT".to_string()],
51            allowed_headers: Some(vec![
52                "Authorization".to_string(),
53                "Content-Type".to_string(),
54            ]),
55            expose_headers: Some(vec!["ETag".to_string()]),
56            max_age_seconds: Some(600),
57        };
58
59        let json = serde_json::to_string(&rule).expect("serialize rule");
60        assert!(json.contains("allowedOrigins"));
61        assert!(json.contains("maxAgeSeconds"));
62
63        let decoded: CorsRule = serde_json::from_str(&json).expect("deserialize rule");
64        assert_eq!(decoded.id.as_deref(), Some("web-app"));
65        assert_eq!(
66            decoded.allowed_origins,
67            vec!["https://app.example.com".to_string()]
68        );
69        assert_eq!(
70            decoded.allowed_methods,
71            vec!["GET".to_string(), "PUT".to_string()]
72        );
73        assert_eq!(decoded.max_age_seconds, Some(600));
74    }
75
76    #[test]
77    fn test_cors_configuration_serialization() {
78        let config = CorsConfiguration {
79            rules: vec![CorsRule {
80                id: None,
81                allowed_origins: vec!["*".to_string()],
82                allowed_methods: vec!["GET".to_string()],
83                allowed_headers: None,
84                expose_headers: None,
85                max_age_seconds: None,
86            }],
87        };
88
89        let json = serde_json::to_string_pretty(&config).expect("serialize config");
90        let decoded: CorsConfiguration = serde_json::from_str(&json).expect("deserialize config");
91        assert_eq!(decoded.rules.len(), 1);
92        assert_eq!(decoded.rules[0].allowed_origins, vec!["*".to_string()]);
93        assert_eq!(decoded.rules[0].allowed_methods, vec!["GET".to_string()]);
94    }
95}