1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use std::time::Duration;

use e2e_test_model::{
    CreateExecutionRequest, CreateStepRequest, Execution, GetExecutionsRequest, Step,
};
pub use reqwest::header::HeaderValue;
use uuid::Uuid;

pub mod model {
    pub use e2e_test_model::*;
}

pub struct Config {
    pub base_url: String,
    pub api_key: Option<HeaderValue>,
}

pub struct Client {
    inner: reqwest_middleware::ClientWithMiddleware,
    config: Config,
}

impl Client {
    pub fn new(config: Config) -> reqwest::Result<Self> {
        let inner = reqwest::Client::builder()
            .timeout(Duration::from_secs(15))
            .connect_timeout(Duration::from_secs(30))
            .build()?;
        let retry_policy = reqwest_retry::policies::ExponentialBackoff {
            max_n_retries: 5,
            min_retry_interval: Duration::from_millis(100),
            max_retry_interval: Duration::from_secs(5),
            backoff_exponent: 2,
        };
        let retry_transient_middleware =
            reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy);
        let inner = reqwest_middleware::ClientBuilder::new(inner)
            .with(retry_transient_middleware)
            .build();

        let mut config = config;
        if let Some(ref mut api_key) = config.api_key {
            api_key.set_sensitive(true);
        }

        Ok(Self { inner, config })
    }

    // TODO handle pagination
    pub async fn get_executions(
        &self,
        params: &GetExecutionsRequest,
    ) -> reqwest_middleware::Result<Vec<Execution>> {
        let resp = self
            .inner
            .get(self.url("/api/executions"))
            .base_headers(&self.config)
            .query(&params)
            .send()
            .await?
            .json()
            .await?;

        Ok(resp)
    }

    pub async fn post_execution(
        &self,
        body: &CreateExecutionRequest,
    ) -> reqwest_middleware::Result<Execution> {
        let resp = self
            .inner
            .post(self.url("/api/executions"))
            .base_headers(&self.config)
            .json(body)
            .send()
            .await?
            .json()
            .await?;

        Ok(resp)
    }

    pub async fn post_step(
        &self,
        uuid: &Uuid,
        body: &CreateStepRequest,
    ) -> reqwest_middleware::Result<Step> {
        let url = format!("/api/executions/{}/steps", uuid);
        let resp = self
            .inner
            .post(self.url(&url))
            .base_headers(&self.config)
            .json(body)
            .send()
            .await?
            .json()
            .await?;

        Ok(resp)
    }

    fn url(&self, uri: &str) -> String {
        format!("{}{}", self.config.base_url, uri)
    }
}

trait BaseHeaders {
    fn base_headers(self, config: &Config) -> Self;
}

impl BaseHeaders for reqwest_middleware::RequestBuilder {
    fn base_headers(self, config: &Config) -> Self {
        if let Some(ref api_key) = config.api_key {
            self.header("Authorization: Bearer", api_key)
        } else {
            self
        }
    }
}