From 8e3b6d495bb0295152cfe1a0e00888731501c048 Mon Sep 17 00:00:00 2001
From: Paul Maruhn <paulmaruhn@posteo.de>
Date: Mon, 27 Dec 2021 15:34:21 +0100
Subject: [PATCH] switch `validity` config option to hours

Use custom deserialization function for duration
---
 src/config.rs   | 61 ++++++++++++++++++++++++++++++++++++++++++++-----
 src/ffdyndns.rs |  2 +-
 2 files changed, 56 insertions(+), 7 deletions(-)

diff --git a/src/config.rs b/src/config.rs
index e7c61a6..9a90040 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,10 +1,14 @@
-use serde::{Serialize, Deserialize};
-use serde::{self, Serializer, Deserializer};
-use std::net::IpAddr;
 use chrono::Duration;
+use serde::{self, Deserialize, Deserializer};
+use serde::de::{self, Visitor};
+use std::fmt;
+use std::marker::PhantomData;
+use std::net::IpAddr;
+use std::str::FromStr;
+// use void::Void;
 
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Deserialize)]
 pub struct Config {
 	pub name: String,
 	pub description: String,
@@ -22,7 +26,7 @@ impl Config {
 	}
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Deserialize)]
 pub struct Domain {
 	/// the domain suffix. eg. for a dynamic domain
 	/// mydomain.ddns.org the name here is ddns.org
@@ -33,5 +37,50 @@ pub struct Domain {
 	/// domain is allowed to updated to
 	pub allowed_ips: Vec<String>,
 	/// duration in days before a subdomain gets 'released`
-	pub validity: usize,
+	#[serde(deserialize_with = "deserialize_duration")]
+	pub validity: Duration,
+}
+
+
+
+fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
+where
+    // T: Deserialize<'de> + FromStr<Err = String>,
+    D: Deserializer<'de>,
+{
+    // This is a Visitor that forwards string types to T's `FromStr` impl and
+    // forwards map types to T's `Deserialize` impl. The `PhantomData` is to
+    // keep the compiler from complaining about T being an unused generic type
+    // parameter. We need T in order to know the Value type for the Visitor
+    // impl.
+    struct DurationDeserializer(PhantomData<fn() -> Duration>);
+
+    impl<'de> Visitor<'de> for DurationDeserializer
+    {
+        type Value = Duration;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            formatter.write_str("string or integer")
+        }
+
+        fn visit_i64<E>(self, value: i64) -> Result<Duration, E>
+        where E: de::Error {
+            Ok(Duration::hours(value))
+        }
+
+        // fn visit_i32<E>(self, value: i32) -> Result<Duration, E>
+        // where E: de::Error {
+        //     Ok(Duration::hours(value as i64))
+        // }
+
+
+        fn visit_str<E>(self, value: &str) -> Result<Duration, E>
+        where
+            E: de::Error,
+        {
+            Ok(Duration::hours(FromStr::from_str(value).unwrap()))
+        }
+    }
+
+    deserializer.deserialize_any(DurationDeserializer(PhantomData))
 }
diff --git a/src/ffdyndns.rs b/src/ffdyndns.rs
index 4ea8f2e..70d0aaf 100644
--- a/src/ffdyndns.rs
+++ b/src/ffdyndns.rs
@@ -111,7 +111,7 @@ impl Service {
 		}
 
 		let token = generate_token();
-		let domain = Domain::new_with_token(&d, token.clone(), Duration::days(CONFIG.get_domain_config(&d.strip_subdomain()).unwrap().validity as i64));
+		let domain = Domain::new_with_token(&d, token.clone(), CONFIG.get_domain_config(&d.strip_subdomain()).unwrap().validity);
 		self.db.insert_new_domain(&domain);
 
 		Ok(token)
-- 
GitLab