1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Types for EPP consolidate request
//!
//! As described in [ConsoliDate mapping](https://www.verisign.com/assets/consolidate-mapping.txt)

use std::fmt;

use chrono::FixedOffset;
use instant_xml::ToXml;

use crate::common::NoExtension;
use crate::domain::update::DomainUpdate;
use crate::request::{Extension, Transaction};

use super::namestore::NameStore;

pub const XMLNS: &str = "http://www.verisign.com/epp/sync-1.0";

impl Transaction<Update> for DomainUpdate<'_> {}

impl Extension for Update {
    type Response = NoExtension;
}

impl Transaction<UpdateWithNameStore<'_>> for DomainUpdate<'_> {}

impl Extension for UpdateWithNameStore<'_> {
    type Response = NameStore<'static>;
}

#[derive(PartialEq, Eq, Debug)]
pub struct GMonthDay {
    pub month: u8,
    pub day: u8,
    pub timezone: Option<FixedOffset>,
}

// Taken from https://github.com/lumeohq/xsd-parser-rs/blob/main/xsd-types/src/types/gmonthday.rs
/// Represents a gMonthDay type <https://www.w3.org/TR/xmlschema-2/#gMonthDay>
impl GMonthDay {
    pub fn new(month: u8, day: u8, timezone: Option<FixedOffset>) -> Result<Self, String> {
        if !(1..=12).contains(&month) {
            return Err("Month value within GMonthDay should lie between 1 and 12".to_string());
        }

        if !(1..=31).contains(&day) {
            return Err("Day value within GMonthDay should lie between 1 and 31".to_string());
        }

        const MONTH_MAX_LEN: [u8; 12] = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        if day > MONTH_MAX_LEN[month as usize - 1] {
            return Err("Day value within GMonthDay is to big for specified month".to_string());
        }

        Ok(Self {
            month,
            day,
            timezone,
        })
    }
}

impl fmt::Display for GMonthDay {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.timezone {
            Some(tz) => write!(f, "--{:02}-{:02}{}", self.month, self.day, tz),
            None => write!(f, "--{:02}-{:02}", self.month, self.day),
        }
    }
}

impl Update {
    /// Create a new sync update request
    pub fn new(expiration: GMonthDay) -> Self {
        Self {
            exp: expiration.to_string(),
        }
    }
}

impl<'a> UpdateWithNameStore<'a> {
    /// Create a new sync update with namestore request
    pub fn new(expiration: GMonthDay, subproduct: &'a str) -> Self {
        Self {
            sync: Update::new(expiration),
            namestore: NameStore::new(subproduct),
        }
    }
}

#[derive(Debug, ToXml)]
#[xml(transparent)]
pub struct UpdateWithNameStore<'a> {
    pub sync: Update,
    pub namestore: NameStore<'a>,
}

/// Type for EPP XML `<consolidate>` extension
#[derive(Debug, ToXml)]
#[xml(rename = "update", ns(XMLNS))]
pub struct Update {
    /// The expiry date of the domain
    #[xml(rename = "expMonthDay")]
    pub exp: String,
}

#[cfg(test)]
mod tests {
    use super::{GMonthDay, Update};
    use crate::domain::update::{DomainChangeInfo, DomainUpdate};
    use crate::extensions::consolidate::UpdateWithNameStore;
    use crate::tests::assert_serialized;

    #[test]
    fn command() {
        let exp = GMonthDay::new(5, 31, None).unwrap();

        let consolidate_ext = Update::new(exp);

        let mut object = DomainUpdate::new("eppdev.com");

        object.info(DomainChangeInfo {
            registrant: None,
            auth_info: None,
        });

        assert_serialized(
            "request/extensions/consolidate.xml",
            (&object, &consolidate_ext),
        );
    }

    #[test]
    fn command_with_namestore() {
        let exp = GMonthDay::new(5, 31, None).unwrap();

        let consolidate_ext = UpdateWithNameStore::new(exp, "com");

        let mut object = DomainUpdate::new("eppdev.com");

        object.info(DomainChangeInfo {
            registrant: None,
            auth_info: None,
        });

        assert_serialized(
            "request/extensions/consolidate_namestore.xml",
            (&object, &consolidate_ext),
        );
    }
}