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
|
use crate::call::{self, Response};
use crate::{Error, Result};
use std::sync::Arc;
use reqwest::{blocking, Url};
/// The INWX environment to use. The Sandbox is good for testing
/// or debugging purposes.
#[derive(Clone, Copy, Debug)]
pub enum Endpoint {
Production,
Sandbox,
}
impl From<Endpoint> for String {
fn from(endpoint: Endpoint) -> String {
match endpoint {
Endpoint::Production => String::from("https://api.domrobot.com/xmlrpc/"),
Endpoint::Sandbox => String::from("https://api.ote.domrobot.com/xmlrpc/"),
}
}
}
impl From<Endpoint> for Url {
fn from(endpoint: Endpoint) -> Self {
String::from(endpoint).parse().unwrap()
}
}
/// A synchronous client to make API calls with.
/// You do **not** need to wrap it in an `Arc` or `Rc`
/// because it already uses an `Arc` internally.
/// [`Rc`]: std::rc::Rc
pub struct Client {
inner: Arc<ClientRef>,
}
impl Client {
/// Initialises a session and returns a `Client` if successful.
pub fn login(ep: Endpoint, user: String, pass: String) -> Result<Client> {
let client = Client {
inner: Arc::new(ClientRef {
http: blocking::Client::builder().cookie_store(true).build()?,
endpoint: ep,
}),
};
client.call(call::account::Login {
user,
pass,
case_insensitive: false,
})?;
Ok(client)
}
/// Issues a `Call` and returns a `Response`
/// if successful and if the status code
/// matches one of the expected status codes.
pub fn call<T, U>(&self, call: T) -> Result<U>
where
T: call::Call + Response<U>,
U: serde::de::DeserializeOwned,
{
let expected = call.expected();
let xml = serde_xmlrpc::request_to_str(&call.method_name(), vec![call])?;
let raw_response = self
.inner
.http
.post::<Url>(self.inner.endpoint.into())
.body(xml)
.send()?
.text()?;
let map = serde_xmlrpc::value_from_str(&raw_response)?;
let resp = map
.as_struct()
.ok_or_else(|| Error::MalformedResponse(map.clone()))?;
let code = resp
.get("code")
.ok_or_else(|| Error::MalformedResponse(map.clone()))?
.as_i32()
.ok_or_else(|| Error::MalformedResponse(map.clone()))?;
if !expected.contains(&code) {
return Err(Error::BadStatus(expected, code));
}
let data = resp
.get("resData")
.ok_or_else(|| Error::MalformedResponse(map.clone()))?;
let res_data = serde_xmlrpc::value_to_string(data.clone())?;
Ok(serde_xmlrpc::response_from_str(&res_data)?)
}
}
impl Drop for Client {
fn drop(&mut self) {
// Ignore the result. Failed logout doesn't really matter.
self.call(call::account::Logout).ok();
}
}
// The underlying data of a `Client`.
struct ClientRef {
http: blocking::Client,
endpoint: Endpoint,
}
|