diff options
| author | Gus Power <gus@infinitesidequests.com> | 2025-05-14 13:21:08 +0100 |
|---|---|---|
| committer | Gus Power <gus@infinitesidequests.com> | 2025-05-14 13:21:08 +0100 |
| commit | dd1483cb6d9c060a17dc68357975de2b1ec09c08 (patch) | |
| tree | dae1850e9ab89894fd217cfcd32ce31d91a767f8 /src | |
| parent | 87c3219c0e6c7f374cf117eb1990dd41e196710a (diff) | |
reduce size of DnsRecordType (introduce enum), add overall config w/ supporting "full" test file.
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.rs | 115 | ||||
| -rw-r--r-- | src/dyndns_service.rs | 12 | ||||
| -rw-r--r-- | src/dyndns_service/gandi.rs | 55 | ||||
| -rw-r--r-- | src/main.rs | 1 |
4 files changed, 160 insertions, 23 deletions
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!"); |
