xt_oss/oss/entities/
website.rs

1use reqwest::StatusCode;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5pub mod builder {
6
7    use super::*;
8
9    #[derive(Debug, Clone, Default)]
10    pub struct MirrorHeadersBuilder<'a> {
11        pass_all: bool,
12        pass: Vec<&'a str>,
13        remove: Vec<&'a str>,
14        set: Vec<(&'a str, &'a str)>,
15    }
16
17    impl<'a> MirrorHeadersBuilder<'a> {
18        pub fn new() -> Self {
19            Self::default()
20        }
21
22        pub fn with_pass_all(mut self, value: bool) -> Self {
23            self.pass_all = value;
24            self
25        }
26
27        pub fn with_pass(mut self, value: Vec<&'a str>) -> Self {
28            self.pass = value;
29            self
30        }
31
32        pub fn with_remove(mut self, value: Vec<&'a str>) -> Self {
33            self.remove = value;
34            self
35        }
36
37        pub fn with_set(mut self, value: Vec<(&'a str, &'a str)>) -> Self {
38            self.set = value;
39            self
40        }
41
42        pub fn build(&self) -> MirrorHeaders {
43            MirrorHeaders {
44                pass_all: Some(self.pass_all),
45                pass: if self.pass.is_empty() {
46                    None
47                } else {
48                    Some(self.pass.iter().map(|value| value.to_string()).collect())
49                },
50                remove: if self.remove.is_empty() {
51                    None
52                } else {
53                    Some(self.remove.iter().map(|value| value.to_string()).collect())
54                },
55                set: if self.set.is_empty() {
56                    None
57                } else {
58                    Some({
59                        self.set
60                            .iter()
61                            .map(|item| Set {
62                                key: item.0.to_string(),
63                                value: item.1.to_string(),
64                            })
65                            .collect()
66                    })
67                },
68            }
69        }
70    }
71
72    #[derive(Debug, Default, Clone)]
73    pub struct RedirectBuilder<'a> {
74        redirect_type: RedirectType,
75        protocol: Option<&'a str>,
76        pass_query_string: Option<&'a str>,
77        replace_key_with: Option<&'a str>,
78        mirror_url: Option<&'a str>,
79        mirror_pass_query_string: Option<bool>,
80        mirror_follow_redirect: Option<bool>,
81        mirror_check_md5: Option<bool>,
82        mirror_headers: Option<MirrorHeaders>,
83        enable_replace_prefix: Option<bool>,
84        mirror_replace_prefix: Option<bool>,
85        http_replace_code: Option<u16>,
86        replace_key_prefix_with: Option<&'a str>,
87        host_name: Option<&'a str>,
88    }
89
90    impl<'a> RedirectBuilder<'a> {
91        pub fn new() -> Self {
92            Self::default()
93        }
94
95        pub fn with_redirect_type(mut self, value: RedirectType) -> Self {
96            self.redirect_type = value;
97            self
98        }
99
100        pub fn with_protocol(mut self, value: &'a str) -> Self {
101            self.protocol = Some(value);
102            self
103        }
104
105        pub fn pass_query_string(mut self, value: &'a str) -> Self {
106            self.pass_query_string = Some(value);
107            self
108        }
109
110        pub fn with_replace_key_with(mut self, value: &'a str) -> Self {
111            self.replace_key_prefix_with = Some(value);
112            self
113        }
114
115        pub fn with_mirror_url(mut self, value: &'a str) -> Self {
116            self.mirror_url = Some(value);
117            self
118        }
119
120        pub fn with_mirror_pass_query_string(mut self, value: bool) -> Self {
121            self.mirror_pass_query_string = Some(value);
122            self
123        }
124
125        pub fn with_mirror_follow_redirect(mut self, value: bool) -> Self {
126            self.mirror_follow_redirect = Some(value);
127            self
128        }
129        pub fn with_mirror_check_md5(mut self, value: bool) -> Self {
130            self.mirror_check_md5 = Some(value);
131            self
132        }
133
134        pub fn with_mirror_headers(mut self, value: MirrorHeaders) -> Self {
135            self.mirror_headers = Some(value);
136            self
137        }
138
139        pub fn with_mirror_replace_prefix(mut self, value: bool) -> Self {
140            self.mirror_replace_prefix = Some(value);
141            self
142        }
143
144        pub fn with_http_replace_code(mut self, value: u16) -> Self {
145            self.http_replace_code = Some(value);
146            self
147        }
148
149        pub fn with_replace_key_prefix_with(mut self, value: &'a str) -> Self {
150            self.replace_key_prefix_with = Some(value);
151            self
152        }
153
154        pub fn with_host_name(mut self, value: &'a str) -> Self {
155            self.host_name = Some(value);
156            self
157        }
158
159        pub fn build(&self) -> Redirect {
160            Redirect {
161                redirect_type: self.redirect_type.clone(),
162                protocol: self.protocol.map(|e| e.to_string()),
163                pass_query_string: self.pass_query_string.map(|e| e.to_string()),
164                replace_key_with: self.replace_key_with.map(|e| e.to_string()),
165                mirror_url: self.mirror_url.map(|e| e.to_string()),
166                mirror_pass_query_string: self.mirror_pass_query_string,
167                mirror_follow_redirect: self.mirror_follow_redirect,
168                mirror_check_md5: self.mirror_check_md5,
169                mirror_headers: self.mirror_headers.clone(),
170                enable_replace_prefix: self.enable_replace_prefix,
171                http_redirect_code: self.http_replace_code,
172                replace_key_prefix_with: self.replace_key_prefix_with.map(|e| e.to_string()),
173                host_name: self.host_name.map(|e| e.to_string()),
174            }
175        }
176    }
177
178    #[derive(Debug, Default)]
179    pub struct ConditionBuilder<'a> {
180        pub key_prefix_equals: Option<&'a str>,
181        pub http_error_code_returned_equals: Option<u16>,
182        pub key_suffix_equals: Option<&'a str>,
183        pub include_header_key: Option<&'a str>,
184        pub include_header_equals: Option<&'a str>,
185    }
186
187    impl<'a> ConditionBuilder<'a> {
188        pub fn new() -> Self {
189            Self::default()
190        }
191
192        pub fn with_key_prefix_equals(mut self, value: &'a str) -> Self {
193            self.key_prefix_equals = Some(value);
194            self
195        }
196
197        pub fn with_http_error_code_returned_equals(mut self, value: u16) -> Self {
198            self.http_error_code_returned_equals = Some(value);
199            self
200        }
201
202        pub fn with_key_suffix_equals(mut self, value: &'a str) -> Self {
203            self.key_suffix_equals = Some(value);
204            self
205        }
206
207        pub fn with_include_header_key(mut self, value: &'a str) -> Self {
208            self.include_header_key = Some(value);
209            self
210        }
211
212        pub fn with_include_header_equals(mut self, value: &'a str) -> Self {
213            self.include_header_equals = Some(value);
214            self
215        }
216
217        pub fn build(&self) -> Condition {
218            Condition {
219                include_header: if let Some(include_header_key) = self.include_header_key {
220                    Some(IncludeHeader {
221                        key: include_header_key.to_string(),
222                        equals: self.include_header_equals.map(|e| e.to_string()),
223                    })
224                } else {
225                    None
226                },
227                key_prefix_equals: self.key_prefix_equals.map(|e| e.to_string()),
228                http_error_code_returned_equals: self.http_error_code_returned_equals,
229                key_suffix_equals: self.key_suffix_equals.map(|e| e.to_string()),
230            }
231        }
232    }
233
234    #[derive(Debug, Default)]
235    pub struct IndexDocumentBuilder<'a> {
236        suffix: &'a str,
237        support_sub_dir: bool,
238        r#type: u16,
239    }
240
241    impl<'a> IndexDocumentBuilder<'a> {
242        pub fn new() -> Self {
243            Self::default()
244        }
245
246        pub fn with_suffix(mut self, value: &'a str) -> Self {
247            self.suffix = value;
248            self
249        }
250
251        pub fn with_support_sub_dir(mut self, value: bool) -> Self {
252            self.support_sub_dir = value;
253            self
254        }
255
256        pub fn with_type(mut self, value: u16) -> Self {
257            self.r#type = value;
258            self
259        }
260
261        pub fn build(&self) -> IndexDocument {
262            IndexDocument {
263                suffix: self.suffix.to_string(),
264                support_sub_dir: Some(self.support_sub_dir),
265                r#type: Some(self.r#type),
266            }
267        }
268    }
269
270    #[derive(Debug, Default)]
271    pub struct ErrorDocumentBuilder<'a> {
272        key: &'a str,
273        http_status: StatusCode,
274    }
275
276    impl<'a> ErrorDocumentBuilder<'a> {
277        pub fn new() -> Self {
278            Self::default()
279        }
280
281        pub fn with_key(mut self, value: &'a str) -> Self {
282            self.key = value;
283            self
284        }
285
286        pub fn with_http_status(mut self, value: StatusCode) -> Self {
287            self.http_status = value;
288            self
289        }
290
291        pub fn build(&self) -> ErrorDocument {
292            ErrorDocument {
293                key: self.key.to_string(),
294                http_status: Some(self.http_status.as_u16()),
295            }
296        }
297    }
298
299    #[derive(Debug, Default, Clone)]
300    pub struct RoutingRuleBuilder {
301        rule: RoutingRule,
302    }
303
304    impl RoutingRuleBuilder {
305        pub fn new() -> Self {
306            Self::default()
307        }
308
309        pub fn with_rule_number(mut self, value: u32) -> Self {
310            self.rule.rule_number = value;
311            self
312        }
313
314        pub fn with_condition(mut self, value: Condition) -> Self {
315            self.rule.condition = value;
316            self
317        }
318        pub fn with_redirect(mut self, value: Redirect) -> Self {
319            self.rule.redirect = value;
320            self
321        }
322
323        pub fn build(&self) -> RoutingRule {
324            self.rule.clone()
325        }
326    }
327
328    #[derive(Debug, Default, Clone)]
329    pub struct RoutingRulesBuilder {
330        rules: Vec<RoutingRule>,
331    }
332
333    impl RoutingRulesBuilder {
334        pub fn new() -> Self {
335            Self::default()
336        }
337
338        pub fn with_rule(mut self, value: RoutingRule) -> Self {
339            self.rules.push(value);
340            self
341        }
342
343        pub fn build(&self) -> RoutingRules {
344            RoutingRules {
345                routing_rule: if self.rules.is_empty() {
346                    None
347                } else {
348                    Some(self.rules.clone())
349                },
350            }
351        }
352    }
353
354    #[derive(Debug, Default)]
355    pub struct WebsiteConfigurationBuilder {
356        pub index_document: Option<IndexDocument>,
357        pub error_document: Option<ErrorDocument>,
358        pub routing_rules: Option<RoutingRules>,
359    }
360
361    impl WebsiteConfigurationBuilder {
362        pub fn new() -> Self {
363            Self::default()
364        }
365
366        pub fn with_index_document(mut self, value: IndexDocument) -> Self {
367            self.index_document = Some(value);
368            self
369        }
370        pub fn with_error_document(mut self, value: ErrorDocument) -> Self {
371            self.error_document = Some(value);
372            self
373        }
374
375        pub fn with_routing_rules(mut self, value: RoutingRules) -> Self {
376            self.routing_rules = Some(value);
377            self
378        }
379
380        pub fn build(&self) -> WebsiteConfiguration {
381            WebsiteConfiguration {
382                index_document: self.index_document.clone(),
383                error_document: self.error_document.clone(),
384                routing_rules: self.routing_rules.clone(),
385            }
386        }
387    }
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize, Default)]
391pub enum RedirectType {
392    #[default]
393    /// 镜像回源
394    Mirror,
395    /// 外部跳转,即OSS会返回一个3xx请求,指定跳转到另外一个地址
396    External,
397    /// 阿里云CDN跳转,主要用于阿里云的CDN。与External不同的是,OSS会额外添加
398    /// 一个Header。 阿里云CDN识别到此Header后会主动跳转到指定的地址,返回给用
399    /// 户获取到的数据,而不是将3xx跳转请求返回给用户
400    AliCDN,
401}
402
403impl fmt::Display for RedirectType {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        write!(
406            f,
407            "{}",
408            match self {
409                Self::Mirror => "Mirror",
410                Self::External => "External",
411                Self::AliCDN => "AliCDN",
412            }
413        )
414    }
415}
416
417#[derive(Debug, Default, Clone, Serialize, Deserialize)]
418pub struct Set {
419    #[serde(rename = "Key")]
420    pub key: String,
421    #[serde(rename = "Value")]
422    pub value: String,
423}
424
425#[derive(Debug, Default, Clone, Serialize, Deserialize)]
426pub struct MirrorHeaders {
427    #[serde(rename = "PassAll", skip_serializing_if = "Option::is_none")]
428    pub pass_all: Option<bool>,
429    #[serde(rename = "Pass", skip_serializing_if = "Option::is_none")]
430    pub pass: Option<Vec<String>>,
431    #[serde(rename = "Remove", skip_serializing_if = "Option::is_none")]
432    pub remove: Option<Vec<String>>,
433    #[serde(rename = "Set", skip_serializing_if = "Option::is_none")]
434    pub set: Option<Vec<Set>>,
435}
436
437#[derive(Debug, Default, Clone, Serialize, Deserialize)]
438pub struct Redirect {
439    #[serde(rename = "RedirectType")]
440    pub redirect_type: RedirectType,
441    #[serde(rename = "Protocol", skip_serializing_if = "Option::is_none")]
442    pub protocol: Option<String>,
443    #[serde(rename = "PassQueryString", skip_serializing_if = "Option::is_none")]
444    pub pass_query_string: Option<String>,
445    #[serde(rename = "ReplaceKeyWith", skip_serializing_if = "Option::is_none")]
446    pub replace_key_with: Option<String>,
447    #[serde(rename = "MirrorURL", skip_serializing_if = "Option::is_none")]
448    pub mirror_url: Option<String>,
449    #[serde(
450        rename = "MirrorPassQueryString",
451        skip_serializing_if = "Option::is_none"
452    )]
453    pub mirror_pass_query_string: Option<bool>,
454    #[serde(
455        rename = "MirrorFollowRedirect",
456        skip_serializing_if = "Option::is_none"
457    )]
458    pub mirror_follow_redirect: Option<bool>,
459    #[serde(rename = "MirrorCheckMd5", skip_serializing_if = "Option::is_none")]
460    pub mirror_check_md5: Option<bool>,
461    #[serde(rename = "MirrorHeaders", skip_serializing_if = "Option::is_none")]
462    pub mirror_headers: Option<MirrorHeaders>,
463    #[serde(
464        rename = "EnableReplacePrefix",
465        skip_serializing_if = "Option::is_none"
466    )]
467    pub enable_replace_prefix: Option<bool>,
468    #[serde(rename = "HttpRedirectCode", skip_serializing_if = "Option::is_none")]
469    pub http_redirect_code: Option<u16>,
470    #[serde(
471        rename = "ReplaceKeyPrefixWith",
472        skip_serializing_if = "Option::is_none"
473    )]
474    pub replace_key_prefix_with: Option<String>,
475    #[serde(rename = "HostName", skip_serializing_if = "Option::is_none")]
476    pub host_name: Option<String>,
477}
478
479#[derive(Debug, Default, Clone, Serialize, Deserialize)]
480pub struct IncludeHeader {
481    #[serde(rename = "Key")]
482    pub key: String,
483    #[serde(rename = "Equals", skip_serializing_if = "Option::is_none")]
484    pub equals: Option<String>,
485}
486
487#[derive(Debug, Default, Clone, Serialize, Deserialize)]
488pub struct Condition {
489    #[serde(rename = "KeyPrefixEquals", skip_serializing_if = "Option::is_none")]
490    pub key_prefix_equals: Option<String>,
491    #[serde(
492        rename = "HttpErrorCodeReturnedEquals",
493        skip_serializing_if = "Option::is_none"
494    )]
495    pub http_error_code_returned_equals: Option<u16>,
496    #[serde(rename = "IncludeHeader", skip_serializing_if = "Option::is_none")]
497    pub include_header: Option<IncludeHeader>,
498    #[serde(rename = "KeySuffixEquals", skip_serializing_if = "Option::is_none")]
499    pub key_suffix_equals: Option<String>,
500}
501
502#[derive(Debug, Default, Clone, Serialize, Deserialize)]
503pub struct RoutingRule {
504    #[serde(rename = "RuleNumber")]
505    pub rule_number: u32,
506    #[serde(rename = "Condition")]
507    pub condition: Condition,
508    #[serde(rename = "Redirect")]
509    pub redirect: Redirect,
510}
511
512#[derive(Debug, Default, Clone, Serialize, Deserialize)]
513pub struct RoutingRules {
514    #[serde(rename = "RoutingRule", skip_serializing_if = "Option::is_none")]
515    pub routing_rule: Option<Vec<RoutingRule>>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
519/// 默认主页的容器
520pub struct IndexDocument {
521    #[serde(rename = "Suffix")]
522    /// 设置默认主页后,如果访问以正斜线(/)结尾的Object,则OSS都会返回此默认主页。
523    pub suffix: String,
524    #[serde(rename = "SupportSubDir", skip_serializing_if = "Option::is_none")]
525    /// 访问子目录时,是否支持跳转到子目录下的默认主页。取值范围如下:
526    pub support_sub_dir: Option<bool>,
527    #[serde(rename = "Type", skip_serializing_if = "Option::is_none")]
528    /// 设置默认主页后,访问以非正斜线(/)结尾的Object,且该Object不存在时的行为。
529    pub r#type: Option<u16>,
530}
531
532impl Default for IndexDocument {
533    fn default() -> Self {
534        Self {
535            suffix: "index.html".to_string(),
536            support_sub_dir: Some(true),
537            r#type: Some(0),
538        }
539    }
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
543pub struct ErrorDocument {
544    #[serde(rename = "Key")]
545    pub key: String,
546    #[serde(rename = "HttpStatus", skip_serializing_if = "Option::is_none")]
547    pub http_status: Option<u16>,
548}
549
550impl Default for ErrorDocument {
551    fn default() -> Self {
552        Self {
553            key: "error.html".to_string(),
554            http_status: Some(StatusCode::NOT_FOUND.as_u16()),
555        }
556    }
557}
558
559#[derive(Debug, Default, Clone, Serialize, Deserialize)]
560pub struct WebsiteConfiguration {
561    // 默认主页的容器
562    #[serde(rename = "IndexDocument", skip_serializing_if = "Option::is_none")]
563    pub index_document: Option<IndexDocument>,
564    #[serde(rename = "ErrorDocument", skip_serializing_if = "Option::is_none")]
565    pub error_document: Option<ErrorDocument>,
566    #[serde(rename = "RoutingRules", skip_serializing_if = "Option::is_none")]
567    pub routing_rules: Option<RoutingRules>,
568}
569
570#[cfg(test)]
571pub mod tests {
572    use reqwest::StatusCode;
573
574    use super::{
575        builder::{
576            ConditionBuilder, ErrorDocumentBuilder, IndexDocumentBuilder, MirrorHeadersBuilder,
577            RedirectBuilder, RoutingRulesBuilder, WebsiteConfigurationBuilder,
578        },
579        RedirectType,
580    };
581    use crate::oss::entities::website::{RoutingRule, Set, WebsiteConfiguration};
582
583    #[test]
584    fn website_configuration_parse_1() {
585        let xml_content = r#"<WebsiteConfiguration>
586<IndexDocument>
587    <Suffix>index.html</Suffix>
588    <SupportSubDir>true</SupportSubDir>
589    <Type>0</Type>
590</IndexDocument>
591<ErrorDocument>
592    <Key>error.html</Key>
593    <HttpStatus>404</HttpStatus>
594</ErrorDocument>
595<RoutingRules>
596    <RoutingRule>
597    <RuleNumber>1</RuleNumber>
598    <Condition>
599        <KeyPrefixEquals>abc/</KeyPrefixEquals>
600        <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
601    </Condition>
602    <Redirect>
603        <RedirectType>Mirror</RedirectType>
604        <PassQueryString>true</PassQueryString>
605        <MirrorURL>http://example.com/</MirrorURL>   
606        <MirrorPassQueryString>true</MirrorPassQueryString>
607        <MirrorFollowRedirect>true</MirrorFollowRedirect>
608        <MirrorCheckMd5>false</MirrorCheckMd5>
609        <MirrorHeaders>
610        <PassAll>true</PassAll>
611        <Pass>myheader-key1</Pass>
612        <Pass>myheader-key2</Pass>
613        <Remove>myheader-key3</Remove>
614        <Remove>myheader-key4</Remove>
615        <Set>
616            <Key>myheader-key5</Key>
617            <Value>myheader-value5</Value>
618        </Set>
619        </MirrorHeaders>
620    </Redirect>
621    </RoutingRule>
622    <RoutingRule>
623    <RuleNumber>2</RuleNumber>
624    <Condition>
625        <KeyPrefixEquals>abc/</KeyPrefixEquals>
626        <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
627        <IncludeHeader>
628        <Key>host</Key>
629        <Equals>test.oss-cn-beijing-internal.aliyuncs.com</Equals>
630        </IncludeHeader>
631    </Condition>
632    <Redirect>
633        <RedirectType>AliCDN</RedirectType>
634        <Protocol>http</Protocol>
635        <HostName>example.com</HostName>
636        <PassQueryString>false</PassQueryString>
637        <ReplaceKeyWith>prefix/${key}.suffix</ReplaceKeyWith>
638        <HttpRedirectCode>301</HttpRedirectCode>
639    </Redirect>
640    </RoutingRule>
641    <RoutingRule>
642    <Condition>
643        <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
644    </Condition>
645    <RuleNumber>3</RuleNumber>
646    <Redirect>
647        <ReplaceKeyWith>prefix/${key}</ReplaceKeyWith>
648        <HttpRedirectCode>302</HttpRedirectCode>
649        <EnableReplacePrefix>false</EnableReplacePrefix>
650        <PassQueryString>false</PassQueryString>
651        <Protocol>http</Protocol>
652        <HostName>example.com</HostName>
653        <RedirectType>External</RedirectType>
654    </Redirect>
655    </RoutingRule>
656</RoutingRules>
657</WebsiteConfiguration>
658"#;
659
660        let object: WebsiteConfiguration = quick_xml::de::from_str(xml_content).unwrap();
661        let left = "index.html";
662        let right = object.index_document.unwrap().suffix;
663        assert_eq!(left, right)
664    }
665
666    #[test]
667    fn website_configuration_parse_2() {
668        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
669<WebsiteConfiguration>
670  <IndexDocument>
671    <Suffix>index.html</Suffix>
672  </IndexDocument>
673  <ErrorDocument>
674    <Key>error.html</Key>
675    <HttpStatus>404</HttpStatus>
676  </ErrorDocument>
677  <RoutingRules>
678    <RoutingRule>
679      <RuleNumber>1</RuleNumber>
680      <Condition>
681        <KeyPrefixEquals>abc/</KeyPrefixEquals>
682        <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
683      </Condition>
684      <Redirect>
685        <RedirectType>Mirror</RedirectType>
686        <PassQueryString>true</PassQueryString>
687        <MirrorURL>http://example.com/</MirrorURL>  
688        <MirrorPassQueryString>true</MirrorPassQueryString>
689        <MirrorFollowRedirect>true</MirrorFollowRedirect>
690        <MirrorCheckMd5>false</MirrorCheckMd5>
691        <MirrorHeaders>
692          <PassAll>true</PassAll>
693          <Pass>myheader-key1</Pass>
694          <Pass>myheader-key2</Pass>
695          <Remove>myheader-key3</Remove>
696          <Remove>myheader-key4</Remove>
697          <Set>
698            <Key>myheader-key5</Key>
699            <Value>myheader-value5</Value>
700          </Set>
701        </MirrorHeaders>
702      </Redirect>
703    </RoutingRule>
704    <RoutingRule>
705      <RuleNumber>2</RuleNumber>
706      <Condition>
707        <IncludeHeader>
708          <Key>host</Key>
709          <Equals>test.oss-cn-beijing-internal.aliyuncs.com</Equals>
710        </IncludeHeader>
711        <KeyPrefixEquals>abc/</KeyPrefixEquals>
712        <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
713      </Condition>
714      <Redirect>
715        <RedirectType>AliCDN</RedirectType>
716        <Protocol>http</Protocol>
717        <HostName>example.com</HostName>
718        <PassQueryString>false</PassQueryString>
719        <ReplaceKeyWith>prefix/${key}.suffix</ReplaceKeyWith>
720        <HttpRedirectCode>301</HttpRedirectCode>
721      </Redirect>
722    </RoutingRule>
723  </RoutingRules>
724</WebsiteConfiguration>"#;
725
726        let object: WebsiteConfiguration = quick_xml::de::from_str(xml_content).unwrap();
727        let left = "index.html";
728        let right = object.index_document.unwrap().suffix;
729        assert_eq!(left, right)
730    }
731
732    #[test]
733    fn mirror_headers_builder() {
734        let obj = MirrorHeadersBuilder::new()
735            .with_pass(["pass1", "pass2"].to_vec())
736            .with_remove(["remove1", "remove2"].to_vec())
737            .with_pass_all(true)
738            .with_set([("lable", "value"), ("label1", "value1")].to_vec())
739            .build();
740        let left = Set {
741            key: "label1".to_string(),
742            value: "value1".to_string(),
743        };
744        let right = &obj.set.unwrap()[1];
745        assert_eq!(left.key, right.key);
746    }
747
748    #[test]
749    fn redirect_builder() {
750        let redirect = RedirectBuilder::new()
751            .with_host_name("xuetube.com")
752            .with_http_replace_code(302)
753            .with_mirror_check_md5(true)
754            .with_mirror_follow_redirect(false)
755            .with_mirror_headers(
756                MirrorHeadersBuilder::new()
757                    .with_pass(["pass1", "pass2"].to_vec())
758                    .with_remove(["remove1", "remove2"].to_vec())
759                    .with_pass_all(true)
760                    .with_set([("name", "sjy"), ("age", "18")].to_vec())
761                    .build(),
762            )
763            .with_mirror_pass_query_string(false)
764            .with_mirror_replace_prefix(false)
765            .with_mirror_url("http://example.com")
766            .with_protocol("https")
767            .with_redirect_type(RedirectType::AliCDN)
768            .with_replace_key_with("test")
769            .build();
770        let left = "pass1";
771        let right = &redirect.mirror_headers.unwrap().pass.unwrap()[0];
772        assert_eq!(left, right);
773    }
774
775    #[test]
776    fn condition_builder() {
777        let condition = ConditionBuilder::new()
778            .with_include_header_key("key")
779            .with_include_header_equals("test")
780            .with_http_error_code_returned_equals(203)
781            .with_key_prefix_equals("prefix")
782            .with_key_suffix_equals("suffix")
783            .build();
784        let left = "key";
785        let right = condition.include_header.unwrap().key;
786        assert_eq!(left, right);
787    }
788
789    #[test]
790    fn web_config_builder() {
791        let config = WebsiteConfigurationBuilder::new()
792            .with_index_document(
793                IndexDocumentBuilder::new()
794                    .with_suffix("test")
795                    .with_support_sub_dir(true)
796                    .with_type(200)
797                    .build(),
798            )
799            .with_error_document(
800                ErrorDocumentBuilder::new()
801                    .with_http_status(StatusCode::NOT_FOUND)
802                    .with_key("abcd")
803                    .build(),
804            )
805            .with_routing_rules(
806                RoutingRulesBuilder::new()
807                    .with_rule(RoutingRule {
808                        rule_number: 100,
809                        condition: ConditionBuilder::new().build(),
810                        redirect: RedirectBuilder::new().build(),
811                    })
812                    .build(),
813            )
814            .build();
815        let left = r#"<WebsiteConfiguration><IndexDocument><Suffix>test</Suffix><SupportSubDir>true</SupportSubDir><Type>200</Type></IndexDocument><ErrorDocument><Key>abcd</Key><HttpStatus>404</HttpStatus></ErrorDocument><RoutingRules><RoutingRule><RuleNumber>100</RuleNumber><Condition/><Redirect><RedirectType>Mirror</RedirectType></Redirect></RoutingRule></RoutingRules></WebsiteConfiguration>"#;
816        let right = quick_xml::se::to_string(&config).unwrap();
817        assert_eq!(left, right);
818    }
819}