aboutsummaryrefslogtreecommitdiff
path: root/src
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 /src
parent87c3219c0e6c7f374cf117eb1990dd41e196710a (diff)
reduce size of DnsRecordType (introduce enum), add overall config w/ supporting "full" test file.
Diffstat (limited to 'src')
-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
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!");