dnslib/vendors/technitium/
client.rs1use reqwest::{Response, multipart};
2use serde_json::Value;
3
4use crate::core::error::{Error, Result};
5use crate::core::secret::ApiToken;
6use crate::vendors::http::HttpClient;
7
8#[derive(Clone, Debug)]
9pub struct TechnitiumClient {
10 http: HttpClient,
11}
12
13impl TechnitiumClient {
14 pub fn new(base_url: String, token: ApiToken) -> Result<Self> {
15 Ok(Self {
16 http: HttpClient::new(base_url, token, true)?,
17 })
18 }
19
20 pub fn base_url(&self) -> &str {
21 &self.http.base_url
22 }
23
24 pub async fn get(&self, path: &str, params: &[(&str, &str)]) -> Result<Value> {
26 let req = self.http.get(path).query(params);
27 let resp = self.http.send("GET", path, req).await?;
28 parse_response(resp).await
29 }
30
31 pub async fn post(&self, path: &str, form: &[(&str, &str)]) -> Result<Value> {
33 let req = self.http.post(path).form(form);
34 let resp = self.http.send("POST", path, req).await?;
35 parse_response(resp).await
36 }
37
38 pub async fn get_text(&self, path: &str, params: &[(&str, &str)]) -> Result<String> {
40 let req = self.http.get(path).query(params);
41 let resp = self.http.send("GET", path, req).await?;
42 let status = resp.status();
43 if status.is_success() {
44 return resp.text().await.map_err(Error::Network);
45 }
46 let message = resp
47 .json::<serde_json::Value>()
48 .await
49 .ok()
50 .and_then(|b| {
51 b.get("errorMessage")
52 .and_then(|m| m.as_str())
53 .map(ToOwned::to_owned)
54 })
55 .unwrap_or_else(|| format!("HTTP {}", status.as_u16()));
56 Err(Error::Api { message })
57 }
58
59 pub async fn post_file(
62 &self,
63 path: &str,
64 params: &[(&str, &str)],
65 file_name: String,
66 file_bytes: Vec<u8>,
67 ) -> Result<Value> {
68 let file_part = multipart::Part::bytes(file_bytes)
69 .file_name(file_name)
70 .mime_str("text/plain")
71 .map_err(Error::Mime)?;
72 let form = multipart::Form::new().part("zoneFile", file_part);
73 let req = self.http.post(path).query(params).multipart(form);
74 let resp = self.http.send("POST", path, req).await?;
75 parse_response(resp).await
76 }
77}
78
79async fn parse_response(resp: Response) -> Result<Value> {
80 let status = resp.status();
81 let body: Value = resp.json().await.map_err(|e| {
82 if e.is_decode() {
85 Error::InvalidJson(e)
86 } else {
87 Error::Network(e)
88 }
89 })?;
90
91 match body.get("status").and_then(|s| s.as_str()) {
92 Some("ok") => Ok(body),
93 Some("error") => {
94 let message = body
95 .get("errorMessage")
96 .and_then(|m| m.as_str())
97 .unwrap_or("unknown error")
98 .to_string();
99 Err(Error::Api { message })
100 }
101 _ if !status.is_success() => Err(Error::Http {
102 status: status.as_u16(),
103 body: body.to_string(),
104 }),
105 _ => Ok(body),
106 }
107}