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
|
use crate::{call, 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 &str {
fn from(endpoint: Endpoint) -> &'static str {
match endpoint {
Endpoint::Production => "https://api.domrobot.com/xmlrpc/",
Endpoint::Sandbox => "https://api.ote.domrobot.com/xmlrpc/",
}
}
}
impl From<Endpoint> for Url {
fn from(endpoint: Endpoint) -> Self {
Url::parse(endpoint.into()).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: &str, pass: &str) -> 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(&self, call: impl call::Call) -> Result<response::Response> {
let expected = call.expected();
let transport = self.inner.http.post::<Url>(self.inner.endpoint.into());
let request = xmlrpc::Request::new(call.method_name()).arg(call);
let raw = request.call(transport)?;
match raw {
xmlrpc::Value::Struct(map) => {
let code = map.get("code").ok_or(Error::Inexistent("code"))?;
match code {
xmlrpc::Value::Int(code) => {
if expected.contains(code) {
let data = map.get("resData").ok_or(Error::Inexistent("resData"))?;
match data {
xmlrpc::Value::Struct(response) => Ok(response::Response {
status: *code,
data: response.clone(),
}),
_ => Err(Error::Type("resData", "Struct", data.clone())),
}
} else {
Err(Error::BadStatus(expected, *code))
}
}
_ => Err(Error::Type("code", "Int", code.clone())),
}
}
_ => Err(Error::BadResponse(raw.clone())),
}
}
}
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,
}
|