1use reqwest::Client;
2use thiserror::Error;
3
4use crate::api::{
5 CreateDnsRecordRequest, CreateDnsRecordResponse, DnsRecord, DnsRecordId, GeneralResponse,
6 ListDnsRecordsResponse, UpdateDnsRecordRequest,
7};
8
9#[derive(Debug, Error)]
13pub enum SimplyClientError {
14 #[error("HTTP error: {0}")]
16 Http(#[from] reqwest::Error),
17 #[error("JSON deserialization error: {0}")]
19 Json(#[from] serde_json::Error),
20 #[error("API error: {0} ({1})")]
22 Api(u32, String),
23}
24
25pub struct SimplyClient {
36 account: String,
37 api_key: String,
38 base_url: String,
39 client: Client,
40}
41
42impl SimplyClient {
43 pub fn new(account: impl Into<String>, api_key: impl Into<String>) -> Self {
51 Self {
52 account: account.into(),
53 api_key: api_key.into(),
54 base_url: "https://api.simply.com/2/".to_string(),
55 client: Client::new(),
56 }
57 }
58}
59
60impl SimplyClient {
61 pub async fn list_dns_records(
68 &self,
69 domain: &str,
70 ) -> Result<Vec<DnsRecord>, SimplyClientError> {
71 let url = format!(
72 "{}/my/products/{}/dns/records",
73 self.base_url.trim_end_matches('/'),
74 domain
75 );
76 let res = self
77 .client
78 .get(&url)
79 .basic_auth(&self.account, Some(&self.api_key))
80 .send()
81 .await?;
82 let resp: ListDnsRecordsResponse = res.json().await?;
83 Ok(resp.records.into_iter().map(|r| r.into()).collect())
84 }
85 pub async fn create_dns_record(
93 &self,
94 domain: &str,
95 req: CreateDnsRecordRequest,
96 ) -> Result<Vec<DnsRecordId>, SimplyClientError> {
97 let url = format!(
98 "{}/my/products/{}/dns/records",
99 self.base_url.trim_end_matches('/'),
100 domain
101 );
102 let res = self
103 .client
104 .post(&url)
105 .basic_auth(&self.account, Some(&self.api_key))
106 .json(&req)
107 .send()
108 .await?;
109 let resp: CreateDnsRecordResponse = res.json().await?;
110 Ok(resp.record.unwrap_or_default())
111 }
112
113 pub async fn update_dns_record(
122 &self,
123 domain: &str,
124 record_id: DnsRecordId,
125 req: UpdateDnsRecordRequest,
126 ) -> Result<(), SimplyClientError> {
127 let url = format!(
128 "{}/my/products/{}/dns/records/{}",
129 self.base_url.trim_end_matches('/'),
130 domain,
131 record_id.id,
132 );
133 let res = self
134 .client
135 .put(&url)
136 .basic_auth(&self.account, Some(&self.api_key))
137 .json(&req)
138 .send()
139 .await?;
140 let status = res.status();
141 if !status.is_success() {
142 let resp: GeneralResponse = res.json().await?;
143 return Err(SimplyClientError::Api(
144 status.as_u16().into(),
145 resp.message.unwrap_or_default(),
146 ));
147 }
148 Ok(())
149 }
150
151 pub async fn delete_dns_record(
159 &self,
160 domain: &str,
161 record_id: DnsRecordId,
162 ) -> Result<(), SimplyClientError> {
163 let url = format!(
164 "{}/my/products/{}/dns/records/{}",
165 self.base_url.trim_end_matches('/'),
166 domain,
167 record_id.id,
168 );
169 let res = self
170 .client
171 .delete(&url)
172 .basic_auth(&self.account, Some(&self.api_key))
173 .send()
174 .await
175 .map_err(SimplyClientError::Http)?;
176 let status = res.status();
177 if !status.is_success() {
178 let resp: GeneralResponse = res.json().await?;
179 return Err(SimplyClientError::Api(
180 status.as_u16().into(),
181 resp.message.unwrap_or_default(),
182 ));
183 }
184 Ok(())
185 }
186}