netcup_client/
client.rs

1use anyhow::{bail, Result};
2use serde::{Deserialize, Serialize};
3use std::marker::PhantomData;
4
5use crate::{
6    actions::{
7        dnsrecord::*,
8        dnszone::*,
9        login::LoginAction,
10        logout::LogoutAction,
11        request::{RequestAction, RequestActionBuilder},
12    },
13    models::{
14        dnsrecord::DnsRecordSet,
15        login::LoginData,
16        responses::{NCDataResponse, NCResponse},
17    },
18    prelude::{DnsRecord, DnsZone},
19};
20
21pub struct Unauthorized;
22pub struct Authorized;
23
24const BASE_URL: &str = "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON";
25
26pub struct NetcupClient<State = Unauthorized> {
27    api_key: String,
28    customer_no: u64,
29    session_id: String,
30    state: PhantomData<State>,
31}
32
33impl NetcupClient {
34    pub fn new(api_key: &str, customer_no: u64) -> Self {
35        NetcupClient {
36            api_key: api_key.to_owned(),
37            customer_no,
38            session_id: String::default(),
39            state: PhantomData,
40        }
41    }
42}
43
44impl<State> NetcupClient<State> {
45    pub fn get_session_id(&self) -> String {
46        self.session_id.to_string()
47    }
48
49    async fn send<Action>(&self, request: &RequestAction<Action>) -> Result<serde_json::Value>
50    where
51        Action: Serialize,
52    {
53        let response: serde_json::Value = reqwest::Client::new()
54            .post(BASE_URL)
55            .json(request)
56            .send()
57            .await?
58            .json()
59            .await?;
60        Ok(response)
61    }
62
63    fn get_response(&self, value: &str) -> Result<NCResponse> {
64        let data = serde_json::from_str::<NCResponse>(value).expect("JSON was not well-formatted");
65        Ok(data)
66    }
67
68    fn get_response_data<'a, Data>(&self, value: &'a str) -> Result<NCDataResponse<Data>>
69    where
70        Data: Deserialize<'a>,
71    {
72        let result = serde_json::from_str::<NCDataResponse<Data>>(value);
73        if result.is_err() {
74            let err_result = self.get_response(value)?;
75            bail!("{} - {}", err_result.short_message, err_result.long_message);
76        }
77        let data = result.expect("JSON was not well-formatted");
78        Ok(data)
79    }
80}
81
82impl NetcupClient<Unauthorized> {
83    pub async fn login(self, api_password: &str) -> Result<NetcupClient<Authorized>> {
84        let param = LoginAction::new(self.customer_no, &self.api_key, api_password);
85        let request = RequestActionBuilder::build("login", param);
86
87        let response = self.send::<LoginAction>(&request).await?;
88        let data = self.get_response_data::<LoginData>(&response.to_string())?;
89
90        let client = NetcupClient {
91            api_key: self.api_key,
92            customer_no: self.customer_no,
93            session_id: data.data.api_session_id,
94            state: PhantomData,
95        };
96        Ok(client)
97    }
98}
99
100impl NetcupClient<Authorized> {
101    pub async fn logout(self) -> Result<NetcupClient<Unauthorized>> {
102        let param = LogoutAction::new(self.customer_no, &self.api_key, &self.session_id);
103        let request = RequestActionBuilder::build("logout", param);
104
105        let response = self.send::<LogoutAction>(&request).await?;
106        let data = self.get_response(&response.to_string())?;
107
108        if data.status.eq("error") {
109            bail!("{} - {}", data.short_message, data.long_message);
110        }
111
112        let client = NetcupClient {
113            api_key: self.api_key,
114            customer_no: self.customer_no,
115            session_id: String::default(),
116            state: PhantomData,
117        };
118        Ok(client)
119    }
120
121    pub async fn get_dns_zone(&self, domain_name: &str) -> Result<DnsZone> {
122        let param = InfoDnsZoneAction::new(
123            self.customer_no,
124            &self.api_key,
125            &self.session_id,
126            domain_name,
127        );
128        let request = RequestActionBuilder::build("infoDnsZone", param);
129
130        let response = self.send::<InfoDnsZoneAction>(&request).await?;
131        let data = self.get_response_data::<DnsZone>(&response.to_string())?;
132
133        Ok(data.data)
134    }
135
136    pub async fn update_dns_zone(&self, dns_zone: DnsZone) -> Result<DnsZone> {
137        let param =
138            UpdateDnsZoneAction::new(self.customer_no, &self.api_key, &self.session_id, dns_zone);
139        let request = RequestActionBuilder::build("updateDnsZone", param);
140
141        let response = self.send::<UpdateDnsZoneAction>(&request).await?;
142        let data = self.get_response_data::<DnsZone>(&response.to_string())?;
143
144        Ok(data.data)
145    }
146
147    pub async fn get_dns_records(&self, domain_name: &str) -> Result<Vec<DnsRecord>> {
148        let param = InfoDnsRecordsAction::new(
149            self.customer_no,
150            &self.api_key,
151            &self.session_id,
152            domain_name,
153        );
154        let request = RequestActionBuilder::build("infoDnsRecords", param);
155
156        let response = self.send::<InfoDnsRecordsAction>(&request).await?;
157        let data = self.get_response_data::<DnsRecordSet>(&response.to_string())?;
158
159        Ok(data.data.records)
160    }
161
162    pub async fn update_dns_records(
163        &self,
164        domain_name: &str,
165        records: Vec<DnsRecord>,
166    ) -> Result<()> {
167        if records.is_empty() {
168            bail!("No DNS records provided.");
169        } else if records.len() > 20 {
170            bail!("More than 20 entries are currently not supported.")
171        }
172
173        let param = UpdateDnsRecordsAction::new(
174            self.customer_no,
175            &self.api_key,
176            &self.session_id,
177            domain_name,
178            DnsRecordSet { records },
179        );
180        let request = RequestActionBuilder::build("updateDnsRecords", param);
181
182        let response = self.send::<UpdateDnsRecordsAction>(&request).await?;
183        let data = self.get_response(&response.to_string())?;
184
185        if data.status.eq("error") {
186            bail!("{} - {}", data.short_message, data.long_message);
187        }
188
189        Ok(())
190    }
191}