aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGus Power <gus@infinitesidequests.com>2025-05-14 13:21:08 +0100
committerGus Power <gus@infinitesidequests.com>2025-05-14 13:21:08 +0100
commitdd1483cb6d9c060a17dc68357975de2b1ec09c08 (patch)
treedae1850e9ab89894fd217cfcd32ce31d91a767f8
parent87c3219c0e6c7f374cf117eb1990dd41e196710a (diff)
reduce size of DnsRecordType (introduce enum), add overall config w/ supporting "full" test file.
-rw-r--r--Cargo.lock254
-rw-r--r--Cargo.toml3
-rw-r--r--src/config.rs115
-rw-r--r--src/dyndns_service.rs12
-rw-r--r--src/dyndns_service/gandi.rs55
-rw-r--r--src/main.rs1
-rw-r--r--test/full-config.json26
7 files changed, 222 insertions, 244 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f5dbdfc..2d47202 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -193,12 +193,6 @@ dependencies = [
]
[[package]]
-name = "data-encoding"
-version = "2.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
-
-[[package]]
name = "deadpool"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -247,24 +241,6 @@ dependencies = [
]
[[package]]
-name = "endian-type"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
-
-[[package]]
-name = "enum-as-inner"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -317,6 +293,15 @@ dependencies = [
]
[[package]]
+name = "fqdn"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f5d7f7b3eed2f771fc7f6fcb651f9560d7b0c483d75876082acb4649d266b3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -721,16 +706,6 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
-dependencies = [
- "unicode-bidi",
- "unicode-normalization",
-]
-
-[[package]]
-name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
@@ -854,13 +829,14 @@ dependencies = [
name = "multiwan-dyndns"
version = "0.1.0"
dependencies = [
+ "fqdn",
"http",
"reqwest",
"serde",
"serde_json",
"serde_with",
+ "strum",
"tokio",
- "trust-dns-client",
"wiremock",
]
@@ -882,15 +858,6 @@ dependencies = [
]
[[package]]
-name = "nibble_vec"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
-dependencies = [
- "smallvec",
-]
-
-[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1014,15 +981,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
-name = "ppv-lite86"
-version = "0.2.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
-dependencies = [
- "zerocopy",
-]
-
-[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1047,46 +1005,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
-name = "radix_trie"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
-dependencies = [
- "endian-type",
- "nibble_vec",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.16",
-]
-
-[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1396,6 +1314,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
+name = "strum"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1467,26 +1407,6 @@ dependencies = [
]
[[package]]
-name = "thiserror"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1528,21 +1448,6 @@ dependencies = [
]
[[package]]
-name = "tinyvec"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
-dependencies = [
- "tinyvec_macros",
-]
-
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-
-[[package]]
name = "tokio"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1636,22 +1541,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
- "tracing-attributes",
"tracing-core",
]
[[package]]
-name = "tracing-attributes"
-version = "0.1.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1661,77 +1554,18 @@ dependencies = [
]
[[package]]
-name = "trust-dns-client"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14135e72c7e6d4c9b6902d4437881a8598f0145dbb2e3f86f92dbad845b61e63"
-dependencies = [
- "cfg-if",
- "data-encoding",
- "futures-channel",
- "futures-util",
- "once_cell",
- "radix_trie",
- "rand",
- "thiserror",
- "tokio",
- "tracing",
- "trust-dns-proto",
-]
-
-[[package]]
-name = "trust-dns-proto"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
-dependencies = [
- "async-trait",
- "cfg-if",
- "data-encoding",
- "enum-as-inner",
- "futures-channel",
- "futures-io",
- "futures-util",
- "idna 0.4.0",
- "ipnet",
- "once_cell",
- "rand",
- "smallvec",
- "thiserror",
- "tinyvec",
- "tokio",
- "tracing",
- "url",
-]
-
-[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
-name = "unicode-bidi"
-version = "0.3.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
-
-[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
-name = "unicode-normalization"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
-dependencies = [
- "tinyvec",
-]
-
-[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1744,7 +1578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
- "idna 1.0.3",
+ "idna",
"percent-encoding",
]
@@ -2154,26 +1988,6 @@ dependencies = [
]
[[package]]
-name = "zerocopy"
-version = "0.8.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index e94ffb9..399d42e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,10 +10,11 @@ license = "MIT OR Apache-2.0"
reqwest = "0.12.15"
tokio = "1.45.0"
http = "1.3.1"
-trust-dns-client = "0.23.2"
serde_json = "1.0.140"
serde = { version = "1.0.219", features = ["derive"] }
serde_with = "3.12.0"
+fqdn = { version = "0.4.6", features = ["serde"] }
+strum = { version = "0.27.1", features = ["derive"] }
[dev-dependencies]
wiremock = "0.6.3"
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..9230f38
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,115 @@
+use crate::dyndns_service::DynDnsProvider;
+use fqdn::FQDN;
+use serde::{Deserialize, Serialize};
+use serde_with::DisplayFromStr;
+use serde_with::serde_as;
+use strum::Display;
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(transparent)]
+struct Config {
+ wans: Vec<WanConfig>
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+struct WanConfig {
+ interface: Option<String>,
+ #[serde(flatten)]
+ dns_record: DnsRecord,
+ providers: Vec<DynDnsProvider>,
+}
+
+#[derive(Debug, Display, Deserialize, Serialize)]
+#[derive(PartialEq)]
+pub enum DnsRecordType {
+ A,
+ AAAA
+}
+
+#[serde_as]
+#[derive(Debug, Deserialize, Serialize)]
+struct DnsRecord {
+ #[serde_as(as = "DisplayFromStr")]
+ fqdn: FQDN,
+ #[serde(default = "default_ttl")]
+ ttl: u32,
+ #[serde(default = "default_record_type")]
+ record_type: DnsRecordType,
+}
+
+fn default_record_type() -> DnsRecordType {
+ DnsRecordType::A
+}
+fn default_ttl() -> u32 {
+ 300
+}
+
+#[cfg(test)]
+mod tests {
+ use std::error::Error;
+ use std::fs::{read_dir, File};
+ use std::io::BufReader;
+ use super::*;
+ use serde_json::json;
+ use std::str::FromStr;
+ use crate::dyndns_service::DynDnsProvider::GANDI;
+ use crate::dyndns_service::gandi::Gandi;
+
+ #[test]
+ fn check_minimal_config() {
+ let input = json!({
+ "fqdn": "dyn.domain.com",
+ "providers": [ {
+ "type": "GANDI",
+ "api_key": "SOME-API-KEY",
+ } ]
+ });
+
+ let wan_config = serde_json::from_value::<WanConfig>(input).unwrap();
+
+ assert_eq!(
+ wan_config.dns_record.fqdn,
+ FQDN::from_str("dyn.domain.com").unwrap()
+ );
+ assert_eq!(wan_config.interface, None);
+
+ assert_eq!(wan_config.providers.len(), 1);
+ let expected = Gandi::new("SOME-API-KEY".to_string());
+ let actual = wan_config.providers.get(0).unwrap();
+ assert_eq!(&GANDI(expected), actual);
+ }
+
+ #[test]
+ fn check_defaults_on_dns_record_deserialization() {
+ let input = json!({
+ "fqdn": "dyn.mydomain.com",
+ });
+
+ let dns_record = serde_json::from_value::<DnsRecord>(input).unwrap();
+
+ assert_eq!(dns_record.record_type, default_record_type());
+ assert_eq!(dns_record.ttl, default_ttl());
+
+ assert_eq!(dns_record.fqdn, FQDN::from_str("dyn.mydomain.com").unwrap());
+ }
+
+ #[test]
+ fn check_file_configs() -> Result<(), Box<dyn Error>> {
+ let path = std::path::Path::new("test");
+ for entry in read_dir(path)? {
+ let entry = entry?;
+ let test_file_path = entry.path();
+ if test_file_path.extension().unwrap_or_default() != "json" {
+ continue;
+ }
+
+ let file = File::open(test_file_path)?;
+ let reader = BufReader::new(file);
+
+ let actual: Config = serde_json::from_reader(reader)?;
+ assert_eq!(actual.wans.len(), 2);
+ }
+ Ok(())
+ }
+
+}
diff --git a/src/dyndns_service.rs b/src/dyndns_service.rs
index 7c903e0..fac34e7 100644
--- a/src/dyndns_service.rs
+++ b/src/dyndns_service.rs
@@ -1,7 +1,15 @@
-mod gandi;
+pub mod gandi;
use reqwest::ClientBuilder;
use std::error::Error;
+use serde::{Deserialize, Serialize};
+use crate::dyndns_service::gandi::Gandi;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+#[serde(tag = "type")]
+pub enum DynDnsProvider {
+ GANDI(Gandi)
+}
struct DynDnsService {}
@@ -13,7 +21,7 @@ impl DynDnsService {
}
}
-trait DynDnsServiceConfiguration {
+pub trait DynDnsServiceConfiguration {
fn get_service_url(&self) -> String;
}
diff --git a/src/dyndns_service/gandi.rs b/src/dyndns_service/gandi.rs
index 72c95ad..03ecf6f 100644
--- a/src/dyndns_service/gandi.rs
+++ b/src/dyndns_service/gandi.rs
@@ -4,54 +4,67 @@
// --header 'content-type: application/json' \
// --data "{ \"rrset_ttl\": 300, \"rrset_values\": [\"1.2.3.4\"] }"
+use crate::dyndns_service::DynDnsServiceConfiguration;
+use http::Method;
+use serde::{Deserialize, Serialize};
use serde_with::DisplayFromStr;
-use serde_with::TryFromInto;
use serde_with::serde_as;
-use http::Method;
-use trust_dns_client::rr::RecordType;
-use serde::Deserialize;
-use crate::dyndns_service::DynDnsServiceConfiguration;
+use crate::config::DnsRecordType;
// See https://api.gandi.net/docs/livedns/ for more info
-
-const API_URL_TEMPLATE: &str = "https://api.gandi.net/api/v5/domains/{domain}/records/{subdomain}/{record_type}";
-
#[serde_as]
-#[derive(Debug, Deserialize)]
-struct Gandi {
- domain: String,
- subdomain: String,
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct Gandi {
+ api_key: String,
#[serde(default = "Gandi::default_method")]
#[serde_as(as = "DisplayFromStr")]
method: Method,
#[serde(default = "Gandi::default_record_type")]
- #[serde_as(as = "DisplayFromStr")]
- record_type: RecordType,
+ record_type: DnsRecordType,
}
impl Gandi {
+ pub fn new(api_key: String) -> Self {
+ Self {
+ api_key,
+ method: Self::default_method(),
+ record_type: Self::default_record_type(),
+ }
+ }
- fn default_method() -> Method { Method::PUT }
- fn default_record_type() -> RecordType { RecordType::A }
-
+ fn default_method() -> Method {
+ Method::PUT
+ }
+ fn default_record_type() -> DnsRecordType {
+ DnsRecordType::A
+ }
}
impl DynDnsServiceConfiguration for Gandi {
fn get_service_url(&self) -> String {
- format!("https://api.gandi.net/api/v5/domains/{domain}/records/{subdomain}/{record_type}", domain = self.domain, subdomain = self.subdomain, record_type = self.record_type)
+ format!(
+ "https://api.gandi.net/api/v5/domains/{domain}/records/{subdomain}/{record_type}",
+ domain = 'a',
+ subdomain = 'b',
+ record_type = self.record_type
+ )
}
}
#[cfg(test)]
mod tests {
-
+ use serde_json::json;
use super::*;
#[test]
fn check_defaults() {
- let gandi = serde_json::from_str::<Gandi>("{ \"domain\":\"mydomain.com\", \"subdomain\":\"dyn\" }").unwrap();
+ let input = json!({
+ "api_key": "SOME-API-KEY",
+ });
+
+ let gandi = serde_json::from_value::<Gandi>(input).unwrap();
- assert_eq!(gandi.record_type, RecordType::A);
+ assert_eq!(gandi.record_type, DnsRecordType::A);
assert_eq!(gandi.method, Method::PUT);
}
}
diff --git a/src/main.rs b/src/main.rs
index 1ae22b8..62d2f38 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
mod dyndns_service;
mod ip_service;
+mod config;
fn main() {
println!("Hello, world!");
diff --git a/test/full-config.json b/test/full-config.json
new file mode 100644
index 0000000..fab3129
--- /dev/null
+++ b/test/full-config.json
@@ -0,0 +1,26 @@
+[
+ {
+ "interface": "eth0",
+ "fqdn": "vpn.private-client.com",
+ "ttl": 600,
+ "record_type": "A",
+ "providers": [
+ {
+ "type": "GANDI",
+ "api_key": "SOME_API_KEY"
+ }
+ ]
+ },
+ {
+ "interface": "eth1",
+ "fqdn": "vpn2.private-client2.com",
+ "ttl": 1800,
+ "record_type": "AAAA",
+ "providers": [
+ {
+ "type": "GANDI",
+ "api_key": "SOME_OTHER_API_KEY"
+ }
+ ]
+ }
+]