elf_utils 0.1.4

elf_rust utils
Documentation
use reqwest::blocking::{Client, Response};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::thread;
use std::time::Duration;

pub trait Reporter {
    fn start(&self, uk: &str) -> Result<u64, Box<dyn Error>>;
    fn finish(&self, uk: &str, msg: &str) -> Result<(), Box<dyn Error>>;
}

#[derive(Clone)]
pub struct HttpReporter {
    client: Client,
    host: String,
    name: String,
}

impl HttpReporter {
    pub fn new(host: String, name: String) -> Self {
        let client = Client::builder()
            .timeout(Duration::from_secs(10))
            .danger_accept_invalid_certs(true)
            .build()
            .unwrap();

        HttpReporter { client, host, name }
    }

    fn retry_request<F>(&self, request_fn: F) -> Result<Response, reqwest::Error>
    where
        F: Fn() -> reqwest::blocking::RequestBuilder,
    {
        let mut retries = 3;
        let mut wait_time = Duration::from_secs(5);

        while retries > 0 {
            let response = request_fn().send();

            match response {
                Ok(resp) => {
                    if resp.status() == StatusCode::OK || resp.status().is_success() {
                        return Ok(resp);
                    }
                    if resp.status().is_server_error() {
                        thread::sleep(wait_time);
                        retries -= 1;
                        wait_time *= 2;
                    } else {
                        return Ok(resp);
                    }
                }
                Err(e) => {
                    if retries == 1 {
                        return Err(e);
                    }
                    thread::sleep(wait_time);
                    retries -= 1;
                    wait_time *= 2;
                }
            }
        }

        request_fn().send()
    }
}

impl Reporter for HttpReporter {
    fn start(&self, uk: &str) -> Result<u64, Box<dyn Error>> {
        if self.host.is_empty() {
            return Ok(0);
        }

        #[derive(Deserialize)]
        struct RespData {
            id: String,
            code: Option<i64>,
            message: Option<String>,
        }

        #[derive(Serialize)]
        struct ReqBody<'a> {
            action: &'a str,
            uk_function_name: &'a str,
            uk: &'a str,
        }

        let body = ReqBody {
            action: "start",
            uk_function_name: &self.name,
            uk,
        };

        let response = self
            .retry_request(|| {
                self.client
                    .post(format!("{}/v1/dashboard/report", self.host))
                    .json(&body)
            })?;

        let status = response.status();
        let resp_data: RespData = response.json()?;

        if status != StatusCode::OK {
            return Err(format!("resp status code: {}", status).into());
        }

        if resp_data.code.is_some() {
            return Err(format!("resp code: {}, msg: {}", resp_data.code.unwrap(), resp_data.message.unwrap_or("".to_string())).into());
        }

        Ok(resp_data.id.parse()?)
    }

    fn finish(&self, uk: &str, msg: &str) -> Result<(), Box<dyn Error>> {
        if self.host.is_empty() {
            return Ok(());
        }

        #[derive(Serialize)]
        struct ReqBody<'a> {
            action: &'a str,
            uk_function_name: &'a str,
            uk: &'a str,
            message: &'a str,
        }

        let body = ReqBody {
            action: "finished",
            uk_function_name: &self.name,
            uk,
            message: msg,
        };

        let response = self
            .retry_request(|| {
                self.client
                    .post(format!("{}/v1/dashboard/report", self.host))
                    .json(&body)
            })?;

        if response.status() != StatusCode::OK {
            return Err(format!("resp status code: {}", response.status()).into());
        }

        Ok(())
    }
}