1mod http;
2pub mod errors;
3
4
5#[cfg(feature = "dnsimple")]
6pub mod dnsimple;
7#[cfg(feature = "gandi")]
8pub mod gandi;
9
10use std::{fmt::{self, Debug, Display, Formatter}};
11
12use serde::{de::DeserializeOwned, Deserialize, Serialize};
13use tracing::warn;
14
15use crate::errors::Result;
16
17
18pub struct Config {
19 pub domain: String,
20 pub dry_run: bool,
21}
22
23#[derive(Serialize, Deserialize, Clone, Debug)]
24pub enum RecordType {
25 A,
26 AAAA,
27 CAA,
28 CNAME,
29 HINFO,
30 MX,
31 NAPTR,
32 NS,
33 PTR,
34 SRV,
35 SPF,
36 SSHFP,
37 TXT,
38}
39
40impl Display for RecordType {
41 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
42 write!(f, "{:?}", self)
43 }
44}
45
46#[allow(unused)]
47pub trait DnsProvider {
48 fn get_record<T>(&self, rtype: RecordType, host: &str) -> impl Future<Output = Result<Option<T>>>
49 where
50 T: DeserializeOwned;
51
52 fn create_record<T>(&self, rtype: RecordType, host: &str, record: &T) -> impl Future<Output = Result<()>>
53 where
54 T: Serialize + DeserializeOwned + Display + Clone + Send + Sync;
55
56 fn update_record<T>(&self, rtype: RecordType, host: &str, record: &T) -> impl Future<Output = Result<()>>
57 where
58 T: Serialize + DeserializeOwned + Display + Clone + Send + Sync;
59
60 fn delete_record(&self, rtype: RecordType, host: &str) -> impl Future<Output = Result<()>>;
61
62
63 #[allow(async_fn_in_trait)]
68 async fn get_txt_record(&self, host: &str) -> Result<Option<String>> {
69 self.get_record::<String>(RecordType::TXT, host).await
70 .map(|opt| opt.map(|s| strip_quotes(&s)))
71 }
72
73 #[allow(async_fn_in_trait)]
74 async fn create_txt_record(&self, host: &str, record: &String) -> Result<()> {
75 self.create_record(RecordType::TXT, host, record).await
76 }
77
78 #[allow(async_fn_in_trait)]
79 async fn update_txt_record(&self, host: &str, record: &String) -> Result<()> {
80 self.update_record(RecordType::TXT, host, record).await
81 }
82
83 #[allow(async_fn_in_trait)]
84 async fn delete_txt_record(&self, host: &str) -> Result<()> {
85 self.delete_record(RecordType::TXT, host).await
86 }
87}
88
89
90fn strip_quotes(record: &str) -> String {
91 let chars = record.chars();
92 let mut check = chars.clone();
93
94 let first = check.nth(0);
95 let last = check.last();
96
97 if let Some('"') = first && let Some('"') = last {
98 let stripped = chars.skip(1)
99 .take(record.len() - 2)
100 .collect();
101 stripped
102 } else {
103 warn!("Double quotes not found in record string, using whole record.");
104 record.to_string()
105 }
106}
107
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_strip_quotes() -> Result<()> {
115 assert_eq!("abc123".to_string(), strip_quotes("\"abc123\""));
116 assert_eq!("abc123\"", strip_quotes("abc123\""));
117 assert_eq!("\"abc123", strip_quotes("\"abc123"));
118 assert_eq!("abc123", strip_quotes("abc123"));
119
120 Ok(())
121 }
122
123}