instant_epp/
response.rs

1//! Types for EPP responses
2
3use std::fmt::Debug;
4
5use chrono::{DateTime, Utc};
6use instant_xml::{FromXml, Kind};
7
8use crate::common::EPP_XMLNS;
9
10/// Type corresponding to the `<undef>` tag an EPP response XML
11#[derive(Debug, Eq, FromXml, PartialEq)]
12#[xml(rename = "undef", ns(EPP_XMLNS))]
13pub struct Undef;
14
15/// Type corresponding to the `<value>` tag under `<extValue>` in an EPP response XML
16#[derive(Debug, Eq, FromXml, PartialEq)]
17#[xml(rename = "value", ns(EPP_XMLNS))]
18pub struct ResultValue {
19    /// The `<undef>` element
20    pub undef: Undef,
21}
22
23/// Type corresponding to the `<extValue>` tag in an EPP response XML
24#[derive(Debug, Eq, FromXml, PartialEq)]
25#[xml(rename = "extValue", ns(EPP_XMLNS))]
26pub struct ExtValue {
27    /// Data under the `<value>` tag
28    pub value: ResultValue,
29    /// Data under the `<reason>` tag
30    pub reason: String,
31}
32
33/// Type corresponding to the `<result>` tag in an EPP response XML
34#[derive(Debug, Eq, FromXml, PartialEq)]
35#[xml(rename = "result", ns(EPP_XMLNS))]
36pub struct EppResult {
37    /// The result code
38    #[xml(attribute)]
39    pub code: ResultCode,
40    /// The result message
41    #[xml(rename = "msg")]
42    pub message: String,
43    /// Data under the `<extValue>` tag
44    pub ext_value: Option<ExtValue>,
45}
46
47/// Response codes as enumerated in section 3 of RFC 5730
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum ResultCode {
50    CommandCompletedSuccessfully = 1000,
51    CommandCompletedSuccessfullyActionPending = 1001,
52    CommandCompletedSuccessfullyNoMessages = 1300,
53    CommandCompletedSuccessfullyAckToDequeue = 1301,
54    CommandCompletedSuccessfullyEndingSession = 1500,
55    UnknownCommand = 2000,
56    CommandSyntaxError = 2001,
57    CommandUseError = 2002,
58    RequiredParameterMissing = 2003,
59    ParameterValueRangeError = 2004,
60    ParameterValueSyntaxError = 2005,
61    UnimplementedProtocolVersion = 2100,
62    UnimplementedCommand = 2101,
63    UnimplementedOption = 2102,
64    UnimplementedExtension = 2103,
65    BillingFailure = 2104,
66    ObjectIsNotEligibleForRenewal = 2105,
67    ObjectIsNotEligibleForTransfer = 2106,
68    AuthenticationError = 2200,
69    AuthorizationError = 2201,
70    InvalidAuthorizationInformation = 2202,
71    ObjectPendingTransfer = 2300,
72    ObjectNotPendingTransfer = 2301,
73    ObjectExists = 2302,
74    ObjectDoesNotExist = 2303,
75    ObjectStatusProhibitsOperation = 2304,
76    ObjectAssociationProhibitsOperation = 2305,
77    ParameterValuePolicyError = 2306,
78    UnimplementedObjectService = 2307,
79    DataManagementPolicyViolation = 2308,
80    CommandFailed = 2400,
81    CommandFailedServerClosingConnection = 2500,
82    AuthenticationErrorServerClosingConnection = 2501,
83    SessionLimitExceededServerClosingConnection = 2502,
84}
85
86impl ResultCode {
87    pub fn from_u16(code: u16) -> Option<Self> {
88        match code {
89            1000 => Some(Self::CommandCompletedSuccessfully),
90            1001 => Some(Self::CommandCompletedSuccessfullyActionPending),
91            1300 => Some(Self::CommandCompletedSuccessfullyNoMessages),
92            1301 => Some(Self::CommandCompletedSuccessfullyAckToDequeue),
93            1500 => Some(Self::CommandCompletedSuccessfullyEndingSession),
94            2000 => Some(Self::UnknownCommand),
95            2001 => Some(Self::CommandSyntaxError),
96            2002 => Some(Self::CommandUseError),
97            2003 => Some(Self::RequiredParameterMissing),
98            2004 => Some(Self::ParameterValueRangeError),
99            2005 => Some(Self::ParameterValueSyntaxError),
100            2100 => Some(Self::UnimplementedProtocolVersion),
101            2101 => Some(Self::UnimplementedCommand),
102            2102 => Some(Self::UnimplementedOption),
103            2103 => Some(Self::UnimplementedExtension),
104            2104 => Some(Self::BillingFailure),
105            2105 => Some(Self::ObjectIsNotEligibleForRenewal),
106            2106 => Some(Self::ObjectIsNotEligibleForTransfer),
107            2200 => Some(Self::AuthenticationError),
108            2201 => Some(Self::AuthorizationError),
109            2202 => Some(Self::InvalidAuthorizationInformation),
110            2300 => Some(Self::ObjectPendingTransfer),
111            2301 => Some(Self::ObjectNotPendingTransfer),
112            2302 => Some(Self::ObjectExists),
113            2303 => Some(Self::ObjectDoesNotExist),
114            2304 => Some(Self::ObjectStatusProhibitsOperation),
115            2305 => Some(Self::ObjectAssociationProhibitsOperation),
116            2306 => Some(Self::ParameterValuePolicyError),
117            2307 => Some(Self::UnimplementedObjectService),
118            2308 => Some(Self::DataManagementPolicyViolation),
119            2400 => Some(Self::CommandFailed),
120            2500 => Some(Self::CommandFailedServerClosingConnection),
121            2501 => Some(Self::AuthenticationErrorServerClosingConnection),
122            2502 => Some(Self::SessionLimitExceededServerClosingConnection),
123            _ => None,
124        }
125    }
126
127    pub fn is_success(&self) -> bool {
128        use ResultCode::*;
129        matches!(
130            self,
131            CommandCompletedSuccessfully
132                | CommandCompletedSuccessfullyActionPending
133                | CommandCompletedSuccessfullyNoMessages
134                | CommandCompletedSuccessfullyAckToDequeue
135                | CommandCompletedSuccessfullyEndingSession
136        )
137    }
138
139    /// Returns true if this error is likely to persist across similar requests inside the same
140    /// connection or session.
141    ///
142    /// The same command with different arguments might succeed in some cases.
143    pub fn is_persistent(&self) -> bool {
144        use ResultCode::*;
145        match self {
146            // The same command with different arguments will result in the same error
147            UnknownCommand
148            | CommandSyntaxError
149            | RequiredParameterMissing
150            | ParameterValueRangeError
151            | ParameterValueSyntaxError
152            | UnimplementedProtocolVersion
153            | UnimplementedCommand
154            | UnimplementedOption
155            | UnimplementedExtension => true,
156            // The connection is in an unhealthy state
157            CommandFailedServerClosingConnection
158            | AuthenticationErrorServerClosingConnection
159            | SessionLimitExceededServerClosingConnection => true,
160            _ => false,
161        }
162    }
163}
164
165impl<'xml> FromXml<'xml> for ResultCode {
166    fn matches(id: instant_xml::Id<'_>, field: Option<instant_xml::Id<'_>>) -> bool {
167        match field {
168            Some(field) => id == field,
169            None => false,
170        }
171    }
172
173    fn deserialize<'cx>(
174        into: &mut Self::Accumulator,
175        field: &'static str,
176        deserializer: &mut instant_xml::Deserializer<'cx, 'xml>,
177    ) -> Result<(), instant_xml::Error> {
178        let mut value = None;
179        u16::deserialize(&mut value, field, deserializer)?;
180        if let Some(value) = value {
181            *into = match Self::from_u16(value) {
182                Some(value) => Some(value),
183                None => {
184                    return Err(instant_xml::Error::UnexpectedValue(format!(
185                        "unexpected result code '{value}'"
186                    )))
187                }
188            };
189        }
190
191        Ok(())
192    }
193
194    type Accumulator = Option<Self>;
195    const KIND: instant_xml::Kind = Kind::Scalar;
196}
197
198/// Type corresponding to the `<trID>` tag in an EPP response XML
199#[derive(Debug, Eq, FromXml, PartialEq)]
200#[xml(rename = "trID", ns(EPP_XMLNS))]
201pub struct ResponseTRID {
202    /// The client TRID
203    #[xml(rename = "clTRID")]
204    pub client_tr_id: Option<String>,
205    /// The server TRID
206    #[xml(rename = "svTRID")]
207    pub server_tr_id: String,
208}
209
210/// Type corresponding to the `<msgQ>` tag in an EPP response XML
211#[derive(Debug, Eq, FromXml, PartialEq)]
212#[xml(rename = "msgQ", ns(EPP_XMLNS))]
213pub struct MessageQueue {
214    /// The message count
215    #[xml(attribute)]
216    pub count: u32,
217    /// The message ID
218    #[xml(attribute)]
219    pub id: String,
220    /// The message date
221    #[xml(rename = "qDate")]
222    pub date: Option<DateTime<Utc>>,
223    /// The message text
224    #[xml(rename = "msg")]
225    pub message: Option<Message>,
226}
227
228#[derive(Debug, Eq, FromXml, PartialEq)]
229#[xml(rename = "msg", ns(EPP_XMLNS))]
230pub struct Message {
231    #[xml(attribute)]
232    pub lang: Option<String>,
233    #[xml(direct)]
234    pub text: String,
235}
236
237#[derive(Debug, FromXml, PartialEq)]
238/// Type corresponding to the `<response>` tag in an EPP response XML
239/// containing an `<extension>` tag
240#[xml(rename = "response", ns(EPP_XMLNS))]
241pub struct Response<D, E> {
242    /// Data under the `<result>` tag
243    pub result: EppResult,
244    /// Data under the `<msgQ>` tag
245    #[xml(rename = "msgQ")]
246    pub message_queue: Option<MessageQueue>,
247    /// Data under the `<resData>` tag
248    pub res_data: Option<ResponseData<D>>,
249    /// Data under the `<extension>` tag
250    pub extension: Option<Extension<E>>,
251    /// Data under the `<trID>` tag
252    pub tr_ids: ResponseTRID,
253}
254
255#[derive(Debug, Eq, FromXml, PartialEq)]
256#[xml(rename = "resData", ns(EPP_XMLNS))]
257pub struct ResponseData<D> {
258    data: D,
259}
260
261impl<D> ResponseData<D> {
262    pub fn into_inner(self) -> D {
263        self.data
264    }
265}
266
267#[derive(Debug, FromXml, PartialEq)]
268/// Type corresponding to the `<response>` tag in an EPP response XML
269/// without `<msgQ>` or `<resData>` sections. Generally used for error handling
270#[xml(rename = "response", ns(EPP_XMLNS))]
271pub struct ResponseStatus {
272    /// Data under the `<result>` tag
273    pub result: EppResult,
274    #[xml(rename = "trID")]
275    /// Data under the `<trID>` tag
276    pub tr_ids: ResponseTRID,
277}
278
279impl<T, E> Response<T, E> {
280    /// Returns the data under the corresponding `<resData>` from the EPP XML
281    pub fn res_data(&self) -> Option<&T> {
282        match &self.res_data {
283            Some(res_data) => Some(&res_data.data),
284            None => None,
285        }
286    }
287
288    pub fn extension(&self) -> Option<&E> {
289        match &self.extension {
290            Some(extension) => Some(&extension.data),
291            None => None,
292        }
293    }
294
295    /// Returns the data under the corresponding `<msgQ>` from the EPP XML
296    pub fn message_queue(&self) -> Option<&MessageQueue> {
297        match &self.message_queue {
298            Some(queue) => Some(queue),
299            None => None,
300        }
301    }
302}
303
304#[derive(Debug, Eq, FromXml, PartialEq)]
305#[xml(rename = "extension", ns(EPP_XMLNS))]
306pub struct Extension<E> {
307    pub data: E,
308}
309
310#[cfg(test)]
311mod tests {
312    use super::{ResponseStatus, ResultCode};
313    use crate::tests::{get_xml, CLTRID, SVTRID};
314    use crate::xml;
315
316    #[test]
317    fn error() {
318        let xml = get_xml("response/error.xml").unwrap();
319        let object = xml::deserialize::<ResponseStatus>(xml.as_str()).unwrap();
320
321        assert_eq!(object.result.code, ResultCode::ObjectDoesNotExist);
322        assert_eq!(object.result.message, "Object does not exist");
323        assert_eq!(
324            object.result.ext_value.unwrap().reason,
325            "545 Object not found"
326        );
327        assert_eq!(object.tr_ids.client_tr_id.unwrap(), CLTRID);
328        assert_eq!(object.tr_ids.server_tr_id, SVTRID);
329    }
330}