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
115
116
117
|
use crate::{call, response};
use crate::{Error, Result};
use std::collections::BTreeMap;
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(&self, call: impl call::Call) -> Result<response::Response> {
let expected = call.expected();
let transport = self.inner.http.post::<Url>(self.inner.endpoint.into());
let binding = call.method_name();
let request = xmlrpc::Request::new(&binding).arg(call);
let raw = request.call(transport)?;
match raw {
xmlrpc::Value::Struct(map) => {
let code = map
.get("code")
.ok_or_else(|| Error::Inexistent("code".into()))?;
match code {
xmlrpc::Value::Int(code) => {
if expected.contains(code) {
let default = &xmlrpc::Value::Struct(BTreeMap::new());
let data = map.get("resData").unwrap_or(default);
match data {
xmlrpc::Value::Struct(response) => Ok(response::Response {
status: *code,
data: response.clone(),
}),
_ => Err(Error::Type(
"resData".into(),
"Struct".into(),
data.clone(),
)),
}
} else {
Err(Error::BadStatus(expected, *code))
}
}
_ => Err(Error::Type("code".into(), "Int".into(), 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,
}
|