webtoon 0.9.0

Client for interacting with various webtoon websites.
Documentation
use anyhow::anyhow;
use reqwest::{RequestBuilder, Response};
use std::time::Duration;

pub static DEFAULT_USER_AGENT: &str =
    concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

pub struct Retry(RequestBuilder);

impl Retry {
    pub async fn send(self) -> Result<Response, anyhow::Error> {
        let mut tries = 10;
        let mut wait = fastrand::u64(1..=10);

        loop {
            let request = self
                .0
                .try_clone()
                .ok_or_else(|| anyhow!("failed to clone `RequestBuilder` in retry loop"))?;

            let response = request.send().await;

            match response {
                Ok(response) if response.status() == 429 && tries != 0 => {
                    tokio::time::sleep(Duration::from_secs(wait)).await;
                    tries -= 1;
                    wait += 3;
                    wait += fastrand::u64(1..=5);
                }
                Err(_) if tries != 0 => {
                    tokio::time::sleep(Duration::from_secs(wait)).await;
                    tries -= 1;
                    wait += 3;
                    wait += fastrand::u64(1..=5);
                }

                Ok(response) => return Ok(response),
                Err(err) => return Err(err.into()),
            }
        }
    }
}

pub trait IRetry {
    fn retry(self) -> Retry;
}

impl IRetry for RequestBuilder {
    fn retry(self) -> Retry {
        Retry(self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_user_agent_should_be_expected() {
        const AGENT: &str = "webtoon/0.9.0";
        const { assert!(AGENT.len() == DEFAULT_USER_AGENT.len()) }
        assert_eq!(AGENT, DEFAULT_USER_AGENT);
    }
}