From af0dd99ef48f9035418a8ed6ca94759595337736 Mon Sep 17 00:00:00 2001
From: Maybe Waffle <waffle.lapkin@gmail.com>
Date: Sun, 10 Apr 2022 18:46:52 +0400
Subject: [PATCH] Use `Duration` instead of `u32` as the `RetryAfter` field

---
 CHANGELOG.md                     |  2 +-
 src/adaptors/throttle/request.rs | 11 ++-----
 src/errors.rs                    |  8 ++---
 src/types.rs                     | 50 ++++++++++++++++++++++++++++++++
 src/types/response_parameters.rs |  6 ++--
 5 files changed, 62 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d819d83..5afb3b4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Improve `Throttling` adoptor
   - Freeze when getting `RetryAfter(_)` error
   - Retry requests that previously returned `RetryAfter(_)` error
-- `RequestError::RetryAfter` now has a `u32` field instead of `i32`
+- `RequestError::RetryAfter` now has a `Duration` field instead of `i32`
 
 ### Added
 
diff --git a/src/adaptors/throttle/request.rs b/src/adaptors/throttle/request.rs
index a1a78afd..c109b178 100644
--- a/src/adaptors/throttle/request.rs
+++ b/src/adaptors/throttle/request.rs
@@ -1,9 +1,4 @@
-use std::{
-    future::Future,
-    pin::Pin,
-    sync::Arc,
-    time::{Duration, Instant},
-};
+use std::{future::Future, pin::Pin, sync::Arc, time::Instant};
 
 use futures::{
     future::BoxFuture,
@@ -188,7 +183,7 @@ where
 
         let retry_after = res.as_ref().err().and_then(<_>::retry_after);
         if let Some(retry_after) = retry_after {
-            let after = Duration::from_secs(retry_after.into());
+            let after = retry_after;
             let until = Instant::now() + after;
 
             // If we'll retry, we check that worker hasn't died at the start of the loop
@@ -196,7 +191,7 @@ where
             let _ = freeze.send(FreezeUntil { until, after, chat }).await;
 
             if retry {
-                log::warn!("Freezing, before retrying: {}", retry_after);
+                log::warn!("Freezing, before retrying: {:?}", retry_after);
                 tokio::time::sleep_until(until.into()).await;
             }
         }
diff --git a/src/errors.rs b/src/errors.rs
index 2ed570db..5d8c4c1b 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,4 +1,4 @@
-use std::io;
+use std::{io, time::Duration};
 
 use serde::Deserialize;
 use thiserror::Error;
@@ -19,8 +19,8 @@ pub enum RequestError {
 
     /// In case of exceeding flood control, the number of seconds left to wait
     /// before the request can be repeated.
-    #[error("Retry after {0} seconds")]
-    RetryAfter(u32),
+    #[error("Retry after {0:?}")]
+    RetryAfter(Duration),
 
     /// Network error while sending a request to Telegram.
     #[error("A network error: {0}")]
@@ -62,7 +62,7 @@ pub enum DownloadError {
 pub trait AsResponseParameters {
     fn response_parameters(&self) -> Option<ResponseParameters>;
 
-    fn retry_after(&self) -> Option<u32> {
+    fn retry_after(&self) -> Option<Duration> {
         self.response_parameters().and_then(|rp| match rp {
             ResponseParameters::RetryAfter(n) => Some(n),
             _ => None,
diff --git a/src/types.rs b/src/types.rs
index ea1f33c0..e544656c 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -345,3 +345,53 @@ pub(crate) mod option_url_from_string {
         }
     }
 }
+
+pub(crate) mod duration_secs {
+    use std::time::Duration;
+
+    use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+    pub(crate) fn serialize<S>(this: &Duration, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        // match this {
+        //     Some(url) => url.serialize(serializer),
+        //     None => "".serialize(serializer),
+        // }
+        this.as_secs().serialize(serializer)
+    }
+
+    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        u64::deserialize(deserializer).map(Duration::from_secs)
+    }
+
+    #[test]
+    fn test() {
+        #[derive(Serialize, Deserialize)]
+        struct Struct {
+            #[serde(with = "crate::types::duration_secs")]
+            duration: Duration,
+        }
+
+        {
+            let json = r#"{"duration":0}"#;
+            let duration: Struct = serde_json::from_str(json).unwrap();
+            assert_eq!(duration.duration, Duration::from_secs(0));
+            assert_eq!(serde_json::to_string(&duration).unwrap(), json.to_owned());
+
+            let json = r#"{"duration":12}"#;
+            let duration: Struct = serde_json::from_str(json).unwrap();
+            assert_eq!(duration.duration, Duration::from_secs(12));
+            assert_eq!(serde_json::to_string(&duration).unwrap(), json.to_owned());
+
+            let json = r#"{"duration":1234}"#;
+            let duration: Struct = serde_json::from_str(json).unwrap();
+            assert_eq!(duration.duration, Duration::from_secs(1234));
+            assert_eq!(serde_json::to_string(&duration).unwrap(), json.to_owned());
+        }
+    }
+}
diff --git a/src/types/response_parameters.rs b/src/types/response_parameters.rs
index 88f3a355..b792a626 100644
--- a/src/types/response_parameters.rs
+++ b/src/types/response_parameters.rs
@@ -1,3 +1,5 @@
+use std::time::Duration;
+
 use serde::{Deserialize, Serialize};
 
 /// Contains information about why a request was unsuccessful.
@@ -16,7 +18,7 @@ pub enum ResponseParameters {
 
     /// In case of exceeding flood control, the number of seconds left to wait
     /// before the request can be repeated.
-    RetryAfter(u32),
+    RetryAfter(#[serde(with = "crate::types::duration_secs")] Duration),
 }
 
 #[cfg(test)]
@@ -34,7 +36,7 @@ mod tests {
 
     #[test]
     fn retry_after_deserialization() {
-        let expected = ResponseParameters::RetryAfter(123_456);
+        let expected = ResponseParameters::RetryAfter(Duration::from_secs(123_456));
         let actual: ResponseParameters = serde_json::from_str(r#"{"retry_after":123456}"#).unwrap();
 
         assert_eq!(expected, actual);