acick 0.0.2

Tools for programming contests
Documentation
use std::io::Write as _;

use anyhow::Context as _;
use reqwest::blocking::{Client, Request, RequestBuilder, Response};
use retry::{delay, retry, OperationResult};

use crate::{Config, Console, Error, Result};

trait ExecSession {
    fn exec_session(&self, request: Request, conf: &Config) -> Result<Response>;
}

impl ExecSession for Client {
    fn exec_session(&self, mut request: Request, conf: &Config) -> Result<Response> {
        let mut storage = conf
            .open_cookie_storage()
            .context("Could not open cookie storage")?;
        storage
            .load_into(&mut request)
            .context("Could not load cookies into request")?;
        let response = self.execute(request)?;
        storage
            .store_from(&response)
            .context("Could not store cookies from response")?;
        Ok(response)
    }
}

pub struct RetryRequestBuilder<'a, 'b> {
    inner: RequestBuilder,
    client: &'a Client,
    conf: &'a Config,
    cnsl: &'a mut Console<'b>,
}

impl<'a, 'b> RetryRequestBuilder<'a, 'b> {
    pub fn send_pretty(&mut self) -> Result<Response> {
        let Self {
            client, conf, cnsl, ..
        } = self;
        let req = self
            .inner
            .try_clone()
            .ok_or_else(|| Error::msg("Could not build request"))?
            .build()?;
        write!(cnsl, "{:7} {} ... ", req.method().as_str(), req.url()).unwrap_or(());
        let result = client
            .exec_session(req, conf)
            .context("Could not send request");
        match &result {
            Ok(res) => writeln!(cnsl, "{}", res.status()),
            Err(_) => writeln!(cnsl, "failed"),
        }
        .unwrap_or(());
        result
    }

    pub fn retry_send(&mut self) -> Result<Response> {
        let session = self.conf.body().session();
        let retry_interval = session.retry_interval().as_millis() as u64;
        let retry_limit = session.retry_limit();
        let durations = delay::Fixed::from_millis(retry_interval).take(retry_limit);
        retry(durations, || match self.send_pretty() {
            Ok(res) => {
                if res.status().is_server_error() {
                    OperationResult::Retry(Error::msg("Received server error"))
                } else {
                    OperationResult::Ok(res)
                }
            }
            Err(err) => OperationResult::Retry(err),
        })
        .map_err(|err| match err {
            retry::Error::Operation { error, .. } => error,
            retry::Error::Internal(msg) => Error::msg(msg),
        })
    }
}

pub trait WithRetry {
    fn with_retry<'a, 'b>(
        self,
        client: &'a Client,
        conf: &'a Config,
        cnsl: &'a mut Console<'b>,
    ) -> RetryRequestBuilder<'a, 'b>;
}

impl WithRetry for RequestBuilder {
    fn with_retry<'a, 'b>(
        self,
        client: &'a Client,
        conf: &'a Config,
        cnsl: &'a mut Console<'b>,
    ) -> RetryRequestBuilder<'a, 'b> {
        RetryRequestBuilder {
            inner: self,
            client,
            conf,
            cnsl,
        }
    }
}