Documentation
use serde::{Deserialize, Serialize};

pub mod builder {
    use super::*;
    use crate::{
        oss::http,
        util::{AllowedHeaderItem, AllowedMethodItem, AllowedOriginItem},
    };

    #[derive(Default, Debug, Clone)]
    pub struct CORSRuleBuilder {
        pub rule: CORSRule,
    }
    impl<'a> CORSRuleBuilder {
        pub fn new() -> Self {
            Self::default()
        }

        pub fn with_allowed_origin(mut self, value: AllowedOriginItem) -> Self {
            match value {
                AllowedOriginItem::Any => self.rule.allowed_origin.push("*".to_string()),
                AllowedOriginItem::Urls(urls) => urls
                    .iter()
                    .for_each(|entry| self.rule.allowed_origin.push(entry.to_string())),
            }
            self
        }

        pub fn with_allowed_method(mut self, value: AllowedMethodItem) -> Self {
            match value {
                AllowedMethodItem::Any => {
                    [
                        http::Method::GET,
                        http::Method::PUT,
                        http::Method::POST,
                        http::Method::DELETE,
                        http::Method::HEAD,
                    ]
                    .into_iter()
                    .for_each(|entry| self.rule.allowed_method.push(entry.to_string()));
                }
                AllowedMethodItem::Methods(methods) => {
                    methods
                        .into_iter()
                        .for_each(|entry| self.rule.allowed_method.push(entry.to_string()));
                }
            }
            self
        }

        pub fn with_allowed_header(mut self, value: AllowedHeaderItem) -> Self {
            match value {
                AllowedHeaderItem::Any => {
                    self.rule.allowed_header = Some(vec!["*".to_string()]);
                }
                AllowedHeaderItem::Headers(headers) => {
                    let allowed_headers = headers
                        .into_iter()
                        .map(|entry| entry.to_string())
                        .collect::<Vec<String>>();
                    self.rule.allowed_header = Some(allowed_headers);
                }
            }
            self
        }

        pub fn with_expose_header(mut self, value: Vec<&'a str>) -> Self {
            self.rule.expose_header = Some(value.into_iter().map(|e| e.to_string()).collect());
            self
        }

        pub fn with_max_age_seconds(mut self, value: u32) -> Self {
            self.rule.max_age_seconds = Some(value);
            self
        }

        pub fn build(self) -> CORSRule {
            self.rule
        }
    }

    #[derive(Default, Debug, Clone)]
    pub struct CORSConfigurationBuilder {
        pub cors_configuration: CORSConfiguration,
    }

    impl CORSConfigurationBuilder {
        pub fn new() -> Self {
            Self::default()
        }

        pub fn add_rule(mut self, value: CORSRule) -> Self {
            self.cors_configuration.cors_rule.push(value);
            self
        }

        pub fn with_response_vary(mut self, value: bool) -> Self {
            self.cors_configuration.response_vary = Some(value);
            self
        }

        pub fn build(self) -> CORSConfiguration {
            self.cors_configuration
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct CORSRule {
    #[serde(rename = "AllowedOrigin")]
    pub allowed_origin: Vec<String>,
    #[serde(rename = "AllowedMethod")]
    pub allowed_method: Vec<String>,
    #[serde(rename = "AllowedHeader")]
    pub allowed_header: Option<Vec<String>>,
    #[serde(rename = "ExposeHeader")]
    pub expose_header: Option<Vec<String>>,
    #[serde(rename = "MaxAgeSeconds")]
    pub max_age_seconds: Option<u32>,
}

#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct CORSConfiguration {
    #[serde(rename = "CORSRule")]
    pub cors_rule: Vec<CORSRule>,
    #[serde(rename = "ResponseVary", skip_serializing_if = "Option::is_none")]
    pub response_vary: Option<bool>,
}
#[cfg(test)]
pub mod tests {
    use super::builder::*;
    use super::*;
    use crate::oss::http;
    use crate::util::{AllowedHeaderItem, AllowedMethodItem, AllowedOriginItem};

    #[test]
    fn allowed_origin_item_1() {
        let value = AllowedMethodItem::Any;
        assert_eq!("*", value.to_string())
    }

    #[test]
    fn allowed_origin_item_2() {
        let value = AllowedOriginItem::Urls(vec!["http://localhost:3000", "http://localhost:3001"]);
        assert_eq!(
            "http://localhost:3000,http://localhost:3001",
            value.to_string()
        );
    }

    #[test]
    fn allowed_method_item_1() {
        let value = AllowedMethodItem::Any;
        assert_eq!("*", value.to_string())
    }

    #[test]
    fn allowed_method_item_2() {
        let value = AllowedMethodItem::Methods(vec![http::Method::GET, http::Method::POST]);
        assert_eq!("GET,POST", &value.to_string());
    }

    #[test]
    fn allowed_header_item_1() {
        let value = AllowedHeaderItem::Any;
        assert_eq!("*", value.to_string())
    }

    #[test]
    fn allowed_header_item_2() {
        let value = AllowedHeaderItem::Headers(vec![
            http::header::CONTENT_DISPOSITION,
            http::header::CONTENT_LANGUAGE,
        ]);
        assert_eq!("content-disposition,content-language", &value.to_string());
    }

    #[test]
    fn cors_configuration_1() {
        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>Authorization</AllowedHeader>
  </CORSRule>
  <CORSRule>
    <AllowedOrigin>http://example.com</AllowedOrigin>
    <AllowedOrigin>http://example.net</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader> Authorization</AllowedHeader>
    <ExposeHeader>x-oss-test</ExposeHeader>
    <ExposeHeader>x-oss-test1</ExposeHeader>
    <MaxAgeSeconds>100</MaxAgeSeconds>
  </CORSRule>
  <ResponseVary>false</ResponseVary>
</CORSConfiguration>"#;

        let object = quick_xml::de::from_str::<CORSConfiguration>(xml_content).unwrap();
        assert_eq!(object.cors_rule[0].allowed_origin[0], "*");
    }

    #[test]
    fn cors_configuration_2() {
        let rule1 = CORSRuleBuilder::new()
            .with_allowed_origin(AllowedOriginItem::Any)
            .with_allowed_method(AllowedMethodItem::Any)
            .with_allowed_header(AllowedHeaderItem::Any)
            .build();

        let rule2 = CORSRuleBuilder::new()
            .with_allowed_origin(AllowedOriginItem::Urls(vec![
                "http://localhost:3000",
                "http://localhost:3001",
            ]))
            .with_allowed_method(AllowedMethodItem::Methods(vec![
                http::Method::GET,
                http::Method::POST,
            ]))
            .with_allowed_header(AllowedHeaderItem::Headers(vec![
                http::header::CACHE_CONTROL,
                http::header::CONTENT_ENCODING,
            ]))
            .with_expose_header(vec!["x-oss-test", "x-oss-test1"])
            .build();

        let config = CORSConfigurationBuilder::new()
            .add_rule(rule1)
            .add_rule(rule2)
            .with_response_vary(false)
            .build();

        let left = quick_xml::se::to_string(&config).unwrap().to_string();

        let right = r#"<CORSConfiguration><CORSRule><AllowedOrigin>*</AllowedOrigin><AllowedMethod>GET</AllowedMethod><AllowedMethod>PUT</AllowedMethod><AllowedMethod>POST</AllowedMethod><AllowedMethod>DELETE</AllowedMethod><AllowedMethod>HEAD</AllowedMethod><AllowedHeader>*</AllowedHeader><ExposeHeader/><MaxAgeSeconds/></CORSRule><CORSRule><AllowedOrigin>http://localhost:3000</AllowedOrigin><AllowedOrigin>http://localhost:3001</AllowedOrigin><AllowedMethod>GET</AllowedMethod><AllowedMethod>POST</AllowedMethod><AllowedHeader>cache-control</AllowedHeader><AllowedHeader>content-encoding</AllowedHeader><ExposeHeader>x-oss-test</ExposeHeader><ExposeHeader>x-oss-test1</ExposeHeader><MaxAgeSeconds/></CORSRule><ResponseVary>false</ResponseVary></CORSConfiguration>"#;

        assert_eq!(left, right)
    }
}