epp_client/
response.rs

1//! Types for EPP responses
2
3use std::fmt::{self, Debug};
4
5use chrono::{DateTime, Utc};
6use serde::Deserialize;
7
8use crate::common::StringValue;
9
10/// Type corresponding to the <undef> tag an EPP response XML
11#[derive(Deserialize, Debug, Eq, PartialEq)]
12pub struct Undef;
13
14/// Type corresponding to the <value> tag under <extValue> in an EPP response XML
15#[derive(Deserialize, Debug, Eq, PartialEq)]
16pub struct ResultValue {
17    /// The XML namespace for the <value> tag
18    #[serde(rename = "xmlns:epp")]
19    xmlns: String,
20    /// The <undef> element
21    pub undef: Undef,
22}
23
24/// Type corresponding to the <extValue> tag in an EPP response XML
25#[derive(Deserialize, Debug, Eq, PartialEq)]
26pub struct ExtValue {
27    /// Data under the <value> tag
28    pub value: ResultValue,
29    /// Data under the <reason> tag
30    pub reason: StringValue<'static>,
31}
32
33/// Type corresponding to the <result> tag in an EPP response XML
34#[derive(Deserialize, Debug, Eq, PartialEq)]
35pub struct EppResult {
36    /// The result code
37    pub code: ResultCode,
38    /// The result message
39    #[serde(rename = "msg")]
40    pub message: StringValue<'static>,
41    /// Data under the <extValue> tag
42    #[serde(rename = "extValue")]
43    pub ext_value: Option<ExtValue>,
44}
45
46/// Response codes as enumerated in section 3 of RFC 5730
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum ResultCode {
49    CommandCompletedSuccessfully = 1000,
50    CommandCompletedSuccessfullyActionPending = 1001,
51    CommandCompletedSuccessfullyNoMessages = 1300,
52    CommandCompletedSuccessfullyAckToDequeue = 1301,
53    CommandCompletedSuccessfullyEndingSession = 1500,
54    UnknownCommand = 2000,
55    CommandSyntaxError = 2001,
56    CommandUseError = 2002,
57    RequiredParameterMissing = 2003,
58    ParameterValueRangeError = 2004,
59    ParameterValueSyntaxError = 2005,
60    UnimplementedProtocolVersion = 2100,
61    UnimplementedCommand = 2101,
62    UnimplementedOption = 2102,
63    UnimplementedExtension = 2103,
64    BillingFailure = 2104,
65    ObjectIsNotEligibleForRenewal = 2105,
66    ObjectIsNotEligibleForTransfer = 2106,
67    AuthenticationError = 2200,
68    AuthorizationError = 2201,
69    InvalidAuthorizationInformation = 2202,
70    ObjectPendingTransfer = 2300,
71    ObjectNotPendingTransfer = 2301,
72    ObjectExists = 2302,
73    ObjectDoesNotExist = 2303,
74    ObjectStatusProhibitsOperation = 2304,
75    ObjectAssociationProhibitsOperation = 2305,
76    ParameterValuePolicyError = 2306,
77    UnimplementedObjectService = 2307,
78    DataManagementPolicyViolation = 2308,
79    CommandFailed = 2400,
80    CommandFailedServerClosingConnection = 2500,
81    AuthenticationErrorServerClosingConnection = 2501,
82    SessionLimitExceededServerClosingConnection = 2502,
83}
84
85impl ResultCode {
86    pub fn from_u16(code: u16) -> Option<Self> {
87        match code {
88            1000 => Some(ResultCode::CommandCompletedSuccessfully),
89            1001 => Some(ResultCode::CommandCompletedSuccessfullyActionPending),
90            1300 => Some(ResultCode::CommandCompletedSuccessfullyNoMessages),
91            1301 => Some(ResultCode::CommandCompletedSuccessfullyAckToDequeue),
92            1500 => Some(ResultCode::CommandCompletedSuccessfullyEndingSession),
93            2000 => Some(ResultCode::UnknownCommand),
94            2001 => Some(ResultCode::CommandSyntaxError),
95            2002 => Some(ResultCode::CommandUseError),
96            2003 => Some(ResultCode::RequiredParameterMissing),
97            2004 => Some(ResultCode::ParameterValueRangeError),
98            2005 => Some(ResultCode::ParameterValueSyntaxError),
99            2100 => Some(ResultCode::UnimplementedProtocolVersion),
100            2101 => Some(ResultCode::UnimplementedCommand),
101            2102 => Some(ResultCode::UnimplementedOption),
102            2103 => Some(ResultCode::UnimplementedExtension),
103            2104 => Some(ResultCode::BillingFailure),
104            2105 => Some(ResultCode::ObjectIsNotEligibleForRenewal),
105            2106 => Some(ResultCode::ObjectIsNotEligibleForTransfer),
106            2200 => Some(ResultCode::AuthenticationError),
107            2201 => Some(ResultCode::AuthorizationError),
108            2202 => Some(ResultCode::InvalidAuthorizationInformation),
109            2300 => Some(ResultCode::ObjectPendingTransfer),
110            2301 => Some(ResultCode::ObjectNotPendingTransfer),
111            2302 => Some(ResultCode::ObjectExists),
112            2303 => Some(ResultCode::ObjectDoesNotExist),
113            2304 => Some(ResultCode::ObjectStatusProhibitsOperation),
114            2305 => Some(ResultCode::ObjectAssociationProhibitsOperation),
115            2306 => Some(ResultCode::ParameterValuePolicyError),
116            2307 => Some(ResultCode::UnimplementedObjectService),
117            2308 => Some(ResultCode::DataManagementPolicyViolation),
118            2400 => Some(ResultCode::CommandFailed),
119            2500 => Some(ResultCode::CommandFailedServerClosingConnection),
120            2501 => Some(ResultCode::AuthenticationErrorServerClosingConnection),
121            2502 => Some(ResultCode::SessionLimitExceededServerClosingConnection),
122            _ => None,
123        }
124    }
125
126    pub fn is_success(&self) -> bool {
127        use ResultCode::*;
128        matches!(
129            self,
130            CommandCompletedSuccessfully
131                | CommandCompletedSuccessfullyActionPending
132                | CommandCompletedSuccessfullyNoMessages
133                | CommandCompletedSuccessfullyAckToDequeue
134                | CommandCompletedSuccessfullyEndingSession
135        )
136    }
137}
138
139impl<'de> Deserialize<'de> for ResultCode {
140    fn deserialize<D>(deserializer: D) -> Result<ResultCode, D::Error>
141    where
142        D: serde::de::Deserializer<'de>,
143    {
144        deserializer.deserialize_u16(ResultCodeVisitor)
145    }
146}
147
148struct ResultCodeVisitor;
149
150impl<'de> serde::de::Visitor<'de> for ResultCodeVisitor {
151    type Value = ResultCode;
152
153    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
154        formatter.write_str("a valid EPP result code")
155    }
156
157    fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
158    where
159        E: serde::de::Error,
160    {
161        use serde::de::Unexpected;
162        ResultCode::from_u16(v).ok_or_else(|| {
163            E::invalid_value(Unexpected::Unsigned(v as u64), &"unexpected result code")
164        })
165    }
166}
167
168/// Type corresponding to the <trID> tag in an EPP response XML
169#[derive(Deserialize, Debug, Eq, PartialEq)]
170pub struct ResponseTRID {
171    /// The client TRID
172    #[serde(rename = "clTRID")]
173    pub client_tr_id: Option<StringValue<'static>>,
174    /// The server TRID
175    #[serde(rename = "svTRID")]
176    pub server_tr_id: StringValue<'static>,
177}
178
179/// Type corresponding to the <msgQ> tag in an EPP response XML
180#[derive(Deserialize, Debug, Eq, PartialEq)]
181pub struct MessageQueue {
182    /// The message count
183    pub count: u32,
184    /// The message ID
185    pub id: String,
186    /// The message date
187    #[serde(rename = "qDate")]
188    pub date: Option<DateTime<Utc>>,
189    /// The message text
190    #[serde(rename = "msg")]
191    pub message: Option<StringValue<'static>>,
192}
193
194#[derive(Deserialize, Debug, Eq, PartialEq)]
195/// Type corresponding to the &lt;response&gt; tag in an EPP response XML
196/// containing an &lt;extension&gt; tag
197pub struct Response<D, E> {
198    /// Data under the <result> tag
199    pub result: EppResult,
200    /// Data under the <msgQ> tag
201    #[serde(rename = "msgQ")]
202    pub message_queue: Option<MessageQueue>,
203    #[serde(rename = "resData")]
204    /// Data under the &lt;resData&gt; tag
205    pub res_data: Option<D>,
206    /// Data under the &lt;extension&gt; tag
207    pub extension: Option<E>,
208    /// Data under the <trID> tag
209    #[serde(rename = "trID")]
210    pub tr_ids: ResponseTRID,
211}
212
213#[derive(Deserialize, Debug, Eq, PartialEq)]
214#[serde(rename = "epp")]
215pub struct ResponseDocument<D, E> {
216    #[serde(rename = "response")]
217    pub data: Response<D, E>,
218}
219
220#[derive(Debug, Deserialize, Eq, PartialEq)]
221#[serde(rename = "epp")]
222pub struct ResultDocument {
223    #[serde(rename = "response")]
224    pub data: ResponseStatus,
225}
226
227#[derive(Deserialize, Debug, Eq, PartialEq)]
228/// Type corresponding to the &lt;response&gt; tag in an EPP response XML
229/// without <msgQ> or &lt;resData&gt; sections. Generally used for error handling
230pub struct ResponseStatus {
231    /// Data under the <result> tag
232    pub result: EppResult,
233    #[serde(rename = "trID")]
234    /// Data under the <trID> tag
235    pub tr_ids: ResponseTRID,
236}
237
238impl<T, E> Response<T, E> {
239    /// Returns the data under the corresponding &lt;resData&gt; from the EPP XML
240    pub fn res_data(&self) -> Option<&T> {
241        match &self.res_data {
242            Some(res_data) => Some(res_data),
243            None => None,
244        }
245    }
246    /// Returns the data under the corresponding <msgQ> from the EPP XML
247    pub fn message_queue(&self) -> Option<&MessageQueue> {
248        match &self.message_queue {
249            Some(queue) => Some(queue),
250            None => None,
251        }
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::{ResultCode, ResultDocument};
258    use crate::tests::{get_xml, CLTRID, SVTRID};
259    use crate::xml;
260
261    #[test]
262    fn error() {
263        let xml = get_xml("response/error.xml").unwrap();
264        let object = xml::deserialize::<ResultDocument>(xml.as_str()).unwrap();
265
266        assert_eq!(object.data.result.code, ResultCode::ObjectDoesNotExist);
267        assert_eq!(object.data.result.message, "Object does not exist".into());
268        assert_eq!(
269            object.data.result.ext_value.unwrap().reason,
270            "545 Object not found".into()
271        );
272        assert_eq!(object.data.tr_ids.client_tr_id.unwrap(), CLTRID.into());
273        assert_eq!(object.data.tr_ids.server_tr_id, SVTRID.into());
274    }
275}