betfair_xml_parser/
operation.rs

1//! Betfair XML file <operation> tag parser
2
3use serde::{Deserialize, Serialize};
4
5use crate::common::{Description, Parameter};
6
7/// Representation of the <operation> tag
8#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(rename_all = "camelCase")]
10pub struct Operation {
11    /// The name of the operation
12    pub name: String,
13    /// Version specifier of when the operation was introduced
14    pub since: Option<String>,
15    /// Potential children of the tag
16    #[serde(rename = "$value")]
17    pub values: Vec<OperationItem>,
18}
19
20/// A child item of the <operation> tag
21#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(rename_all = "camelCase")]
23#[expect(clippy::module_name_repetitions)]
24pub enum OperationItem {
25    /// Description tag
26    Description(Description),
27    /// Parameters tag
28    Parameters(Parameters),
29}
30
31/// Representation of the <parameters> tag
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct Parameters {
34    /// Vector of potential children of the tag
35    #[serde(rename = "$value")]
36    pub values: Vec<ParametersItems>,
37}
38
39/// A child item of the <parameters> tag
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(rename_all = "camelCase")]
42pub enum ParametersItems {
43    /// Request tag
44    Request(Request),
45    /// `SimpleResponse` tag
46    SimpleResponse(SimpleResponse),
47    /// Exceptions tag
48    Exceptions(Exceptions),
49}
50
51/// Representation of the <request> tag
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct Request {
54    /// Vector of potential children of the tag
55    #[serde(rename = "$value")]
56    pub values: Option<Vec<Parameter>>,
57}
58
59/// Representation of the <simpleResponse> tag
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61pub struct SimpleResponse {
62    /// The type of the response
63    pub r#type: String,
64    /// The description of the response
65    pub description: Description,
66}
67
68/// Representation of the <exceptions> tag
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct Exceptions {
71    /// Vector of potential exact Exception tags
72    #[serde(rename = "$value")]
73    pub values: Vec<Exception>,
74}
75
76/// Representation of the <exception> tag
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
78pub struct Exception {
79    /// The type of the exception
80    pub r#type: String,
81    /// The description of the exception
82    pub description: Description,
83}
84
85#[cfg(test)]
86#[expect(clippy::unwrap_used)]
87#[expect(clippy::indexing_slicing)]
88mod tests {
89    use rstest::rstest;
90    use serde_xml_rs::from_str;
91
92    use super::*;
93    use crate::common::ParameterItem;
94
95    #[rstest]
96    fn test_parse_request() {
97        let xml = r#"
98            <request>
99                <parameter mandatory="true" name="filter" type="MarketFilter">
100                    <description>The filter to select desired markets. All markets that match the criteria in the filter
101                        are selected.
102                    </description>
103                </parameter>
104                <parameter name="locale" type="string">
105                    <description>The language used for the response. If not specified, the default is returned.
106                    </description>
107                </parameter>
108            </request>
109    "#;
110
111        let req = from_str::<Request>(xml).unwrap();
112        assert_eq!(req.values.unwrap().len(), 2);
113    }
114
115    #[rstest]
116    fn test_parse_simple_response() {
117        let xml = r#"
118            <simpleResponse type="list(EventTypeResult)">
119                <description>output data</description>
120            </simpleResponse>
121    "#;
122
123        let sr = from_str::<SimpleResponse>(xml).unwrap();
124        assert_eq!(sr.r#type, "list(EventTypeResult)");
125        assert_eq!(sr.description.value.unwrap().as_str(), "output data");
126    }
127
128    #[rstest]
129    fn test_parse_exceptions() {
130        let xml = r#"
131            <exceptions>
132                <exception type="APINGException">
133                    <description>Generic exception that is thrown if this operation fails for any reason.</description>
134                </exception>
135            </exceptions>
136    "#;
137
138        let exceptions = from_str::<Exceptions>(xml).unwrap();
139        assert_eq!(exceptions.values.len(), 1);
140        assert_eq!(exceptions.values[0].r#type, "APINGException".to_owned());
141    }
142
143    #[rstest]
144    fn test_parse_parameters() {
145        let xml = r#"
146        <parameters>
147            <request>
148                <parameter mandatory="true" name="filter" type="MarketFilter">
149                    <description>The filter to select desired markets. All markets that match the criteria in the filter
150                        are selected.
151                    </description>
152                </parameter>
153                <parameter name="locale" type="string">
154                    <description>The language used for the response. If not specified, the default is returned.
155                    </description>
156                </parameter>
157            </request>
158            <simpleResponse type="list(EventTypeResult)">
159                <description>output data</description>
160            </simpleResponse>
161            <exceptions>
162                <exception type="APINGException">
163                    <description>Generic exception that is thrown if this operation fails for any reason.</description>
164                </exception>
165            </exceptions>
166        </parameters>
167    "#;
168
169        let params = from_str::<Parameters>(xml).unwrap();
170        assert_eq!(params.values.len(), 3);
171        assert!(matches!(params.values[0], ParametersItems::Request(_)));
172        assert!(matches!(
173            params.values[1],
174            ParametersItems::SimpleResponse(_)
175        ));
176        assert!(matches!(params.values[2], ParametersItems::Exceptions(_)));
177    }
178
179    #[rstest]
180    fn test_parse_operation() {
181        let xml = r#"
182    <operation name="listEventTypes" since="1.0.0">
183        <description>Returns a list of Event Types (i.e. Sports) associated with the markets selected by the MarketFilter.
184        </description>
185        <parameters>
186            <request>
187                <parameter mandatory="true" name="filter" type="MarketFilter">
188                    <description>The filter to select desired markets. All markets that match the criteria in the filter are selected.
189                    </description>
190                </parameter>
191                <parameter name="locale" type="string">
192                    <description>The language used for the response. If not specified, the default is returned.
193                    </description>
194                </parameter>
195            </request>
196            <simpleResponse type="list(EventTypeResult)">
197                <description>output data</description>
198            </simpleResponse>
199            <exceptions>
200                <exception type="APINGException">
201                    <description>Generic exception that is thrown if this operation fails for any reason.</description>
202                </exception>
203            </exceptions>
204        </parameters>
205    </operation>
206    "#;
207
208        let op = from_str::<Operation>(xml).unwrap();
209        let expected = Operation {
210            name: "listEventTypes".to_owned(),
211            since: Some("1.0.0".to_owned()),
212            values: vec![
213                OperationItem::Description(Description {
214                    value: Some("Returns a list of Event Types (i.e. Sports) associated with the markets selected by the MarketFilter.".to_owned())
215                }),
216                OperationItem::Parameters(Parameters {
217                    values: vec![
218                        ParametersItems::Request(Request {
219                            values: Some(vec![
220                                Parameter {
221                                    mandatory: Some(true),
222                                    name: "filter".to_owned(),
223                                    r#type: "MarketFilter".to_owned(),
224                                    items: vec![
225                                        ParameterItem::Description(Description {
226                                            value: Some("The filter to select desired markets. All markets that match the criteria in the filter are selected.".to_owned())
227                                        })
228                                    ]
229                                },
230                                Parameter {
231                                    mandatory: None,
232                                    name: "locale".to_owned(),
233                                    r#type: "string".to_owned(),
234                                    items: vec![
235                                        ParameterItem::Description(Description {
236                                            value: Some("The language used for the response. If not specified, the default is returned.".to_owned())
237                                        })
238                                    ]
239                                }
240                            ])
241                        }),
242                        ParametersItems::SimpleResponse(SimpleResponse {
243                            r#type: "list(EventTypeResult)".to_owned(),
244                            description: Description {
245                                value: Some("output data".to_owned())
246                            }
247                        }),
248                        ParametersItems::Exceptions(Exceptions {
249                            values: vec![
250                                Exception {
251                                    r#type: "APINGException".to_owned(),
252                                    description: Description {
253                                        value: Some("Generic exception that is thrown if this operation fails for any reason.".to_owned())
254                                    }
255                                }
256                            ]
257                        })
258                    ]
259                })
260            ]
261        };
262        assert_eq!(op, expected);
263    }
264
265    #[rstest]
266    fn test_parse_operation_2() {
267        let xml = r#"
268    <operation name="getDeveloperAppKeys" since="1.0.0">
269        <description>
270            Get all application keys owned by the given developer/vendor
271        </description>
272        <parameters>
273            <request/>
274            <simpleResponse type="list(DeveloperApp)">
275                <description>
276                    A list of application keys owned by the given developer/vendor
277                </description>
278            </simpleResponse>
279            <exceptions>
280                <exception type="AccountAPINGException">
281                    <description>Generic exception that is thrown if this operation fails for any reason.</description>
282                </exception>
283            </exceptions>
284        </parameters>
285    </operation>
286    "#;
287
288        let op = from_str::<Operation>(xml).unwrap();
289        let expected = Operation {
290            name: "getDeveloperAppKeys".to_owned(),
291            since: Some("1.0.0".to_owned()),
292            values: vec![
293                OperationItem::Description(Description {
294                    value: Some("Get all application keys owned by the given developer/vendor".to_owned())
295                }),
296                OperationItem::Parameters(Parameters {
297                    values: vec![
298                        ParametersItems::Request(Request {
299                            values: None
300                        }),
301                        ParametersItems::SimpleResponse(SimpleResponse {
302                            description: Description {
303                                value: Some("A list of application keys owned by the given developer/vendor".to_owned())
304                            },
305                            r#type: "list(DeveloperApp)".to_owned()
306                        }),
307                        ParametersItems::Exceptions(Exceptions {
308                            values: vec![
309                                Exception {
310                                    r#type: "AccountAPINGException".to_owned(),
311                                    description: Description {
312                                        value: Some("Generic exception that is thrown if this operation fails for any reason.".to_owned())
313                                    }
314                                }
315                            ]
316                        })
317                    ]
318                })
319            ]
320        };
321        assert_eq!(op, expected);
322    }
323}