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}